pfstools-2.2.0/0002775000701400070140000000000014105165661012065 5ustar rkm38rkm38pfstools-2.2.0/README.OSX0000664000701400070140000000133714105165602013412 0ustar rkm38rkm38To compile pfstools from sources on OSX, you need: 1. XCode 2. Any of: Homebrew - http://brew.sh/ (recommended) Fink - http://www.finkproject.org/ MacPorts - https://www.macports.org/ Refer to REAME file for the instruction on how to compile with cmake. The build was tested to work on maxOS Sierra 10.12.6 on 13 Nov 2017 with Homebrew. The following notes can help get all dependencies recognized: * Qt5: You need to add "-D CMAKE_PREFIX_PATH=/usr/local/opt/qt5" to cmake. The path should be pointing where the Qt5 is installed. ===== Image Magick ===== In case of problems with ImageMagick on Mountain Lion: brew install libtool --universal brew link libtool --force brew install imagemagick --with-magick-plus-plus pfstools-2.2.0/cmake/0002775000701400070140000000000014105165602013140 5ustar rkm38rkm38pfstools-2.2.0/cmake/FindNETPBM.cmake0000664000701400070140000000253014105165602015726 0ustar rkm38rkm38# - Find NETPBM # Find the native NETPBM includes and library # This module defines # NETPBM_INCLUDE_DIR, where to find jpeglib.h, etc. # NETPBM_LIBRARIES, the libraries needed to use NETPBM. # NETPBM_FOUND, If false, do not try to use NETPBM. # also defined, but not for general use are # NETPBM_LIBRARY, where to find the NETPBM library. FIND_PATH(NETPBM_INCLUDE_DIR pam.h ${NETPBM_ROOT_DIR}/lib /usr/local/include /usr/local/include/netpbm /usr/include /usr/include/netpbm ) SET(NETPBM_NAMES ${NETPBM_NAMES} netpbm) FIND_LIBRARY(NETPBM_LIBRARY NAMES ${NETPBM_NAMES} PATHS /usr/lib /usr/local/lib ) IF (NETPBM_LIBRARY AND NETPBM_INCLUDE_DIR) SET(NETPBM_LIBRARIES ${NETPBM_LIBRARY}) SET(NETPBM_FOUND "YES") ELSE (NETPBM_LIBRARY AND NETPBM_INCLUDE_DIR) SET(NETPBM_FOUND "NO") ENDIF (NETPBM_LIBRARY AND NETPBM_INCLUDE_DIR) IF (NETPBM_FOUND) IF (NOT NETPBM_FIND_QUIETLY) MESSAGE(STATUS "Found NETPBM: ${NETPBM_LIBRARIES}") ENDIF (NOT NETPBM_FIND_QUIETLY) ELSE (NETPBM_FOUND) IF (NETPBM_FIND_REQUIRED) MESSAGE(FATAL_ERROR "Could not find NETPBM library") ENDIF (NETPBM_FIND_REQUIRED) ENDIF (NETPBM_FOUND) # Deprecated declarations. SET (NATIVE_NETPBM_INCLUDE_PATH ${NETPBM_INCLUDE_DIR} ) GET_FILENAME_COMPONENT (NATIVE_NETPBM_LIB_PATH ${NETPBM_LIBRARY} PATH) MARK_AS_ADVANCED( NETPBM_LIBRARY NETPBM_INCLUDE_DIR ) pfstools-2.2.0/cmake/FindGSL.cmake0000664000701400070140000001165314105165602015374 0ustar rkm38rkm38# Try to find gnu scientific library GSL # See # http://www.gnu.org/software/gsl/ and # http://gnuwin32.sourceforge.net/packages/gsl.htm # # Once run this will define: # # GSL_FOUND = system has GSL lib # # GSL_LIBRARIES = full path to the libraries # on Unix/Linux with additional linker flags from "gsl-config --libs" # # CMAKE_GSL_CXX_FLAGS = Unix compiler flags for GSL, essentially "`gsl-config --cxxflags`" # # GSL_INCLUDE_DIR = where to find headers # # GSL_LINK_DIRECTORIES = link directories, useful for rpath on Unix # GSL_EXE_LINKER_FLAGS = rpath on Unix # # Felix Woelk 07/2004 # Jan Woetzel # # www.mip.informatik.uni-kiel.de # -------------------------------- IF(WIN32) # JW tested with gsl-1.8, Windows XP, MSVS 7.1 SET(GSL_POSSIBLE_ROOT_DIRS ${GSL_ROOT_DIR} $ENV{GSL_ROOT_DIR} ${GSL_DIR} ${GSL_HOME} $ENV{GSL_DIR} $ENV{GSL_HOME} $ENV{EXTRA} "C:/Program Files/GnuWin32" ) FIND_PATH(GSL_INCLUDE_DIR NAMES gsl/gsl_cdf.h gsl/gsl_randist.h PATHS ${GSL_POSSIBLE_ROOT_DIRS} PATH_SUFFIXES include DOC "GSL header include dir" ) FIND_LIBRARY(GSL_GSL_LIBRARY NAMES libgsl.dll.a gsl libgsl PATHS ${GSL_POSSIBLE_ROOT_DIRS} PATH_SUFFIXES lib DOC "GSL library" ) if(NOT GSL_GSL_LIBRARY) FIND_FILE(GSL_GSL_LIBRARY NAMES libgsl.dll.a PATHS ${GSL_POSSIBLE_ROOT_DIRS} PATH_SUFFIXES lib DOC "GSL library") endif(NOT GSL_GSL_LIBRARY) FIND_LIBRARY(GSL_GSLCBLAS_LIBRARY NAMES libgslcblas.dll.a gslcblas libgslcblas PATHS ${GSL_POSSIBLE_ROOT_DIRS} PATH_SUFFIXES lib DOC "GSL cblas library dir" ) if(NOT GSL_GSLCBLAS_LIBRARY) FIND_FILE(GSL_GSLCBLAS_LIBRARY NAMES libgslcblas.dll.a PATHS ${GSL_POSSIBLE_ROOT_DIRS} PATH_SUFFIXES lib DOC "GSL library") endif(NOT GSL_GSLCBLAS_LIBRARY) SET(GSL_LIBRARIES ${GSL_GSL_LIBRARY}) #MESSAGE("DBG\n" # "GSL_GSL_LIBRARY=${GSL_GSL_LIBRARY}\n" # "GSL_GSLCBLAS_LIBRARY=${GSL_GSLCBLAS_LIBRARY}\n" # "GSL_LIBRARIES=${GSL_LIBRARIES}") ELSE(WIN32) IF(UNIX) SET(GSL_CONFIG_PREFER_PATH "$ENV{GSL_DIR}/bin" "$ENV{GSL_DIR}" "$ENV{GSL_HOME}/bin" "$ENV{GSL_HOME}" CACHE STRING "preferred path to GSL (gsl-config)") FIND_PROGRAM(GSL_CONFIG gsl-config ${GSL_CONFIG_PREFER_PATH} /usr/bin/ ) # MESSAGE("DBG GSL_CONFIG ${GSL_CONFIG}") IF (GSL_CONFIG) # set CXXFLAGS to be fed into CXX_FLAGS by the user: SET(GSL_CXX_FLAGS "`${GSL_CONFIG} --cflags`") # set INCLUDE_DIRS to prefix+include EXEC_PROGRAM(${GSL_CONFIG} ARGS --prefix OUTPUT_VARIABLE GSL_PREFIX) SET(GSL_INCLUDE_DIR ${GSL_PREFIX}/include CACHE STRING INTERNAL) # set link libraries and link flags #SET(GSL_LIBRARIES "`${GSL_CONFIG} --libs`") EXEC_PROGRAM(${GSL_CONFIG} ARGS --libs OUTPUT_VARIABLE GSL_LIBRARIES ) # extract link dirs for rpath EXEC_PROGRAM(${GSL_CONFIG} ARGS --libs OUTPUT_VARIABLE GSL_CONFIG_LIBS ) # extract version EXEC_PROGRAM(${GSL_CONFIG} ARGS --version OUTPUT_VARIABLE GSL_FULL_VERSION ) # split version as major/minor STRING(REGEX MATCH "(.)\\..*" GSL_VERSION_MAJOR_ "${GSL_FULL_VERSION}") SET(GSL_VERSION_MAJOR ${CMAKE_MATCH_1}) STRING(REGEX MATCH ".\\.(.*)" GSL_VERSION_MINOR_ "${GSL_FULL_VERSION}") SET(GSL_VERSION_MINOR ${CMAKE_MATCH_1}) # split off the link dirs (for rpath) # use regular expression to match wildcard equivalent "-L*" # with is a space or a semicolon STRING(REGEX MATCHALL "[-][L]([^ ;])+" GSL_LINK_DIRECTORIES_WITH_PREFIX "${GSL_CONFIG_LIBS}" ) # MESSAGE("DBG GSL_LINK_DIRECTORIES_WITH_PREFIX=${GSL_LINK_DIRECTORIES_WITH_PREFIX}") # remove prefix -L because we need the pure directory for LINK_DIRECTORIES IF (GSL_LINK_DIRECTORIES_WITH_PREFIX) STRING(REGEX REPLACE "[-][L]" "" GSL_LINK_DIRECTORIES ${GSL_LINK_DIRECTORIES_WITH_PREFIX} ) ENDIF (GSL_LINK_DIRECTORIES_WITH_PREFIX) SET(GSL_EXE_LINKER_FLAGS "-Wl,-rpath,${GSL_LINK_DIRECTORIES}" CACHE STRING INTERNAL) # MESSAGE("DBG GSL_LINK_DIRECTORIES=${GSL_LINK_DIRECTORIES}") # MESSAGE("DBG GSL_EXE_LINKER_FLAGS=${GSL_EXE_LINKER_FLAGS}") # ADD_DEFINITIONS("-DHAVE_GSL") # SET(GSL_DEFINITIONS "-DHAVE_GSL") MARK_AS_ADVANCED( GSL_CXX_FLAGS GSL_INCLUDE_DIR GSL_LIBRARIES GSL_LINK_DIRECTORIES GSL_DEFINITIONS ) MESSAGE(STATUS "Using GSL from ${GSL_PREFIX}") ELSE(GSL_CONFIG) MESSAGE("FindGSL.cmake: gsl-config not found. Please set it manually. GSL_CONFIG=${GSL_CONFIG}") ENDIF(GSL_CONFIG) ENDIF(UNIX) ENDIF(WIN32) IF(GSL_LIBRARIES) IF(GSL_INCLUDE_DIR OR GSL_CXX_FLAGS) SET(GSL_FOUND 1) ENDIF(GSL_INCLUDE_DIR OR GSL_CXX_FLAGS) ENDIF(GSL_LIBRARIES) pfstools-2.2.0/cmake/FindMATLAB.cmake0000664000701400070140000000733214105165602015706 0ustar rkm38rkm38# This is a much simplified version of the standard CMake FindMatlab # script, intended to be used with 'mex' compilation #============================================================================= SET(MATLAB_FOUND 0) SET(MATLAB_ROOT "" CACHE PATH "Matlab root directory") IF(NOT MATLAB_ROOT) IF(WIN32) #Find Matlab root. Suppose the Matlab version is between 7.1 and 8.15 #SET(MATLAB_ROOT "") SET(MATLAB_INCLUDE_DIR "") SET(_MATLAB_INCLUDE_DIR "") SET(REGISTRY_KEY_TMPL "[HKEY_LOCAL_MACHINE\\SOFTWARE\\MathWorks\\MATLAB\\TMPL_MAJOR.TMPL_MINOR;MATLABROOT]") # This method will not work with Cygwin FOREACH(MATLAB_MAJOR RANGE 7 8) FOREACH(MATLAB_MINOR RANGE 15) #STRING(REPLACE "TMPL_MINOR" "${MATLAB_MINOR}" REGISTRY_KEY_OUTP ${REGISTRY_KEY_TMPL}) STRING(REPLACE "TMPL_MINOR" "${MATLAB_MINOR}" REGISTRY_KEY_OUTP ${REGISTRY_KEY_TMPL}) STRING(REPLACE "TMPL_MAJOR" "${MATLAB_MAJOR}" REGISTRY_KEY_OUTP ${REGISTRY_KEY_OUTP}) GET_FILENAME_COMPONENT(_MATLAB_ROOT ${REGISTRY_KEY_OUTP} ABSOLUTE) IF(NOT ${_MATLAB_ROOT} STREQUAL "/registry") SET(MATLAB_ROOT ${_MATLAB_ROOT}) ENDIF() ENDFOREACH() ENDFOREACH() ELSE( WIN32 ) # Linux and OSX SET(_MATLAB_ROOT_LST $ENV{MATLABDIR} $ENV{MATLAB_DIR} /usr/local/matlab-7sp1/ /opt/matlab-7sp1/ $ENV{HOME}/matlab-7sp1/ $ENV{HOME}/redhat-matlab/ /usr/local/MATLAB/R2012b/ /usr/local/MATLAB/R2012a/ /usr/local/MATLAB/R2011b/ /usr/local/MATLAB/R2011a/ /usr/local/MATLAB/R2010bSP1/ /usr/local/MATLAB/R2010b/ /usr/local/MATLAB/R2010a/ /usr/local/MATLAB/R2009bSP1/ /usr/local/MATLAB/R2009b/ /usr/local/MATLAB/R2009a/ /usr/local/MATLAB/R2008b/ /usr/local/MATLAB/R2008a/ /usr/local/matlab9b/ /Applications/MATLAB_R2012b.app/ /Applications/MATLAB_R2012a.app/ /Applications/MATLAB_R2011b.app/ /Applications/MATLAB_R2011a.app/ /Applications/MATLAB_R2010bSP1.app/ /Applications/MATLAB_R2010b.app/ /Applications/MATLAB_R2010a.app/ /Applications/MATLAB_R2009bSP1.app/ /Applications/MATLAB_R2009b.app/ /Applications/MATLAB_R2009a.app/ /Applications/MATLAB_R2008b.app/ /Applications/MATLAB_R2008a.app/ ) SET(_MATLAB_ROOT "bin-NOTFOUND") FOREACH(LOOP_VAR ${_MATLAB_ROOT_LST}) FIND_PATH(_MATLAB_ROOT "bin" ${LOOP_VAR} NO_DEFAULT_PATH) IF(_MATLAB_ROOT) SET(MATLAB_ROOT ${_MATLAB_ROOT}) BREAK() ENDIF() ENDFOREACH() ENDIF(WIN32) ENDIF(NOT MATLAB_ROOT) if(NOT MATLAB_ROOT) message(STATUS "Failed to find matlab root directory. Specify it manually by setting MATLAB_ROOT cmake cache variable.") else(NOT MATLAB_ROOT) find_program(MATLAB_MEX mex PATHS ${MATLAB_ROOT}/bin NO_DEFAULT_PATH NO_SYSTEM_ENVIRONMENT_PATH) if(NOT MATLAB_MEX) find_program(MATLAB_MEX mex.bat PATHS ${MATLAB_ROOT}/bin NO_DEFAULT_PATH NO_SYSTEM_ENVIRONMENT_PATH) endif(NOT MATLAB_MEX) if(NOT MATLAB_MEX) message(STATUS "Failed to find mex matlab compiler.") else(NOT MATLAB_MEX) if( CYGWIN ) # We need extra tricks when compiling with VisualStudio from cygwin file(READ ${CMAKE_SOURCE_DIR}/cmake/cygmex.sh file_content) string(REGEX REPLACE "@MATLAB_MEX_EXE@" "${MATLAB_MEX}" file_content "${file_content}") file(WRITE "${CMAKE_BINARY_DIR}/cygmex.sh" "${file_content}") set( MATLAB_MEX "${CMAKE_BINARY_DIR}/cygmex.sh" ) # This is not very portable but we are on cygwin, so it should be OK execute_process( COMMAND chmod a+x "${CMAKE_BINARY_DIR}/cygmex.sh" ) endif( CYGWIN ) set( MATLAB_FOUND ON ) endif(NOT MATLAB_MEX) endif(NOT MATLAB_ROOT) pfstools-2.2.0/cmake/FindTIFF.cmake0000664000701400070140000000136714105165602015500 0ustar rkm38rkm38# - Find TIFF library # Find the native TIFF includes and library # This module defines # TIFF_INCLUDE_DIR, where to find tiff.h, etc. # TIFF_LIBRARIES, libraries to link against to use TIFF. # TIFF_FOUND, If false, do not try to use TIFF. # also defined, but not for general use are # TIFF_LIBRARY, where to find the TIFF library. FIND_PATH(TIFF_INCLUDE_DIR tiff.h) SET(TIFF_NAMES ${TIFF_NAMES} tiff) FIND_LIBRARY(TIFF_LIBRARY NAMES ${TIFF_NAMES} ) # handle the QUIETLY and REQUIRED arguments and set TIFF_FOUND to TRUE if # all listed variables are TRUE INCLUDE(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS(TIFF DEFAULT_MSG TIFF_LIBRARY TIFF_INCLUDE_DIR) IF(TIFF_FOUND) SET( TIFF_LIBRARIES ${TIFF_LIBRARY} ) ENDIF(TIFF_FOUND) pfstools-2.2.0/cmake/FindEXIF.cmake0000664000701400070140000000336014105165602015476 0ustar rkm38rkm38# - Try to find EXIF # Once done this will define # # EXIF_FOUND - system has EXIF # EXIF_INCLUDE_DIRS - the EXIF include directory # EXIF_LIBRARIES - Link these to use EXIF # EXIF_DEFINITIONS - Compiler switches required for using EXIF # # Copyright (c) 2006 Andreas Schneider # # Redistribution and use is allowed according to the terms of the New # BSD license. # For details see the accompanying COPYING-CMAKE-SCRIPTS file. # if (EXIF_LIBRARIES AND EXIF_INCLUDE_DIRS) # in cache already set(EXIF_FOUND TRUE) else (EXIF_LIBRARIES AND EXIF_INCLUDE_DIRS) find_path(EXIF_INCLUDE_DIRS NAMES exif-data.h PATHS /usr/include /usr/local/include /opt/local/include /sw/include /usr/include/libexif /usr/local/include/libexif /opt/local/include/libexif /sw/include/libexif ) # debian uses version suffixes # add suffix evey new release find_library(EXIF_LIBRARIES NAMES exif PATHS /usr/lib /usr/local/lib /opt/local/lib /sw/lib ) if (EXIF_INCLUDE_DIRS AND EXIF_LIBRARIES) set(EXIF_FOUND TRUE) endif (EXIF_INCLUDE_DIRS AND EXIF_LIBRARIES) if (EXIF_FOUND) if (NOT EXIF_FIND_QUIETLY) message(STATUS "Found EXIF: ${EXIF_LIBRARIES}") endif (NOT EXIF_FIND_QUIETLY) else (EXIF_FOUND) if (EXIF_FIND_REQUIRED) message(FATAL_ERROR "Could not find EXIF") endif (EXIF_FIND_REQUIRED) endif (EXIF_FOUND) # show the EXIF_INCLUDE_DIRS and EXIF_LIBRARIES variables only in the advanced view mark_as_advanced(EXIF_INCLUDE_DIRS EXIF_LIBRARIES) endif (EXIF_LIBRARIES AND EXIF_INCLUDE_DIRS) if (WIN32) set(EXIF_FOUND TRUE) set(EXIF_LIBRARIES "") set(EXIF_INCLUDE_DIRS "") endif (WIN32) pfstools-2.2.0/cmake/FindOpenEXR.cmake0000664000701400070140000001020514105165602016217 0ustar rkm38rkm38# - Find OpenEXR library # Find the native OpenEXR includes and library # This module defines # OPENEXR_INCLUDE_DIRS, where to find ImfXdr.h, etc. Set when # OPENEXR_INCLUDE_DIR is found. # OPENEXR_LIBRARIES, libraries to link against to use OpenEXR. # OPENEXR_ROOT_DIR, The base directory to search for OpenEXR. # This can also be an environment variable. # OPENEXR_FOUND, If false, do not try to use OpenEXR. # # For individual library access these advanced settings are available # OPENEXR_HALF_LIBRARY, Path to Half library # OPENEXR_IEX_LIBRARY, Path to Half library # OPENEXR_ILMIMF_LIBRARY, Path to Ilmimf library # OPENEXR_ILMTHREAD_LIBRARY, Path to IlmThread library # OPENEXR_IMATH_LIBRARY, Path to Imath library # # also defined, but not for general use are # OPENEXR_LIBRARY, where to find the OpenEXR library. #============================================================================= # Copyright 2011 Blender Foundation. # # Distributed under the OSI-approved BSD License (the "License"); # see accompanying file Copyright.txt for details. # # This software is distributed WITHOUT ANY WARRANTY; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # See the License for more information. #============================================================================= # If OPENEXR_ROOT_DIR was defined in the environment, use it. IF(NOT OPENEXR_ROOT_DIR AND NOT $ENV{OPENEXR_ROOT_DIR} STREQUAL "") SET(OPENEXR_ROOT_DIR $ENV{OPENEXR_ROOT_DIR}) ENDIF() SET(_openexr_FIND_COMPONENTS Half Iex IlmImf IlmThread Imath ) SET(_openexr_SEARCH_DIRS ${OPENEXR_ROOT_DIR} /usr/local /sw # Fink /opt/local # DarwinPorts /opt/csw # Blastwave "C:/Program Files (x86)/OpenEXR" # Windows ) FIND_PATH(OPENEXR_INCLUDE_DIR NAMES ImfXdr.h HINTS ${_openexr_SEARCH_DIRS} PATH_SUFFIXES include/OpenEXR include ) #message( "Include dir: ${OPENEXR_INCLUDE_DIR}" ) SET(_openexr_LIBRARIES) FOREACH(COMPONENT ${_openexr_FIND_COMPONENTS}) STRING(TOUPPER ${COMPONENT} UPPERCOMPONENT) FIND_LIBRARY(OPENEXR_${UPPERCOMPONENT}_LIBRARY NAMES ${COMPONENT}-2_2 ${COMPONENT}-2.2 ${COMPONENT}-2.1 ${COMPONENT}-2_1 ${COMPONENT} # The newest version first HINTS ${_openexr_SEARCH_DIRS} PATH_SUFFIXES lib64 lib ) # message( "Lib: ${OPENEXR_${UPPERCOMPONENT}_LIBRARY}" ) # IF( NOT "${OPENEXR_${UPPERCOMPONENT}_LIBRARY}" ) # message( "Library ${COMPONENT} not found" ) # SET( OPENEXR_FOUND "" ) # break() # ENDIF() LIST(APPEND _openexr_LIBRARIES OPENEXR_${UPPERCOMPONENT}_LIBRARY) ENDFOREACH() # OpenEXR needs zlib, which could be tricky to locate on Windows # You may need to manually do something like this: #SET(ZLIB_DIR "e:/zlib/") #SET(CMAKE_INCLUDE_PATH ${ZLIB_DIR}/include ${CMAKE_INCLUDE_PATH}) #SET(CMAKE_LIBRARY_PATH ${ZLIB_DIR}/ ${ZLIB_DIR}/lib ${CMAKE_LIBRARY_PATH}) FIND_PACKAGE(ZLIB REQUIRED) LIST(APPEND _openexr_LIBRARIES ZLIB_LIBRARY) # handle the QUIETLY and REQUIRED arguments and set OPENEXR_FOUND to TRUE if # all listed variables are TRUE INCLUDE(FindPackageHandleStandardArgs) #FIND_PACKAGE_HANDLE_STANDARD_ARGS(OpenEXR DEFAULT_MSG # ${_openexr_LIBRARIES} OPENEXR_INCLUDE_DIR) # message( "${_openexr_LIBRARIES}" ) # This is a work-around as passing the list does not work in cmake 2.8.11.2 (cygwin) FIND_PACKAGE_HANDLE_STANDARD_ARGS(OpenEXR DEFAULT_MSG OPENEXR_HALF_LIBRARY OPENEXR_IEX_LIBRARY OPENEXR_ILMIMF_LIBRARY OPENEXR_ILMTHREAD_LIBRARY OPENEXR_IMATH_LIBRARY ZLIB_LIBRARY OPENEXR_INCLUDE_DIR) IF(OPENEXR_FOUND) SET(OPENEXR_LIBRARIES ${OPENEXR_ILMTHREAD_LIBRARY} ${OPENEXR_IEX_LIBRARY} ${OPENEXR_ILMIMF_LIBRARY} ${OPENEXR_IMATH_LIBRARY} ${OPENEXR_HALF_LIBRARY} ${ZLIB_LIBRARY}) # Both include paths are needed because of dummy OSL headers mixing #include and #include :( SET(OPENEXR_INCLUDE_DIRS ${OPENEXR_INCLUDE_DIR} ${OPENEXR_INCLUDE_DIR}/OpenEXR ${ZLIB_INCLUDE_DIR}) ENDIF() MARK_AS_ADVANCED(OPENEXR_INCLUDE_DIR) FOREACH(COMPONENT ${_openexr_FIND_COMPONENTS}) STRING(TOUPPER ${COMPONENT} UPPERCOMPONENT) MARK_AS_ADVANCED(OPENEXR_${UPPERCOMPONENT}_LIBRARY) ENDFOREACH() pfstools-2.2.0/cmake/FindFFTW.cmake0000664000701400070140000000163414105165602015513 0ustar rkm38rkm38# - Find FFTW # Find the native FFTW includes and library # # FFTW_INCLUDES - where to find fftw3.h # FFTW_LIBRARIES - List of libraries when using FFTW. # FFTW_FOUND - True if FFTW found. if (FFTW_INCLUDES) # Already in cache, be silent set (FFTW_FIND_QUIETLY TRUE) endif (FFTW_INCLUDES) find_path (FFTW_INCLUDES fftw3.h) find_library (FFTW3_LIB NAMES fftw3) find_library (FFTW3F_LIB NAMES fftw3f) find_library (FFTW3F_THREADS_LIB NAMES fftw3_threads) #set (FFTW_LIBRARIES ${FFTW3_LIB} ${FFTW3F_LIB}) # handle the QUIETLY and REQUIRED arguments and set FFTW_FOUND to TRUE if # all listed variables are TRUE include (FindPackageHandleStandardArgs) find_package_handle_standard_args (FFTW DEFAULT_MSG FFTW3_LIB FFTW3F_LIB FFTW3F_THREADS_LIB FFTW_INCLUDES) SET(FFTW_LIBS ${FFTW3_LIB} ${FFTW3F_LIB} ${FFTW3F_THREADS_LIB}) mark_as_advanced (FFTW_LIBRARIES FFTW_INCLUDES) pfstools-2.2.0/cmake/cygmex.sh0000775000701400070140000000115414105165602014772 0ustar rkm38rkm38#!/bin/bash # This is a script of tricks to compile matlab interface with mex and Visual Studio when executed from cygwin mexargs="" for arg in $*; do if [[ $arg == *"/"* ]]; then if [[ $arg == "-I"* ]]; then conv_arg="-I`cygpath -m ${arg:2}`" elif [[ $arg == "-L"* ]]; then conv_arg="-L`cygpath -m ${arg:2}`" else conv_arg=`cygpath -m $arg` fi else conv_arg=$arg fi if [[ $conv_arg == *".cpp.o" ]]; then conv_arg=`echo ${conv_arg%.cpp.o}.obj | sed 's/__\/pfs//g'` fi mexargs="$mexargs $conv_arg" done #echo "Running command: @MATLAB_MEX_EXE@ $mexargs" "@MATLAB_MEX_EXE@" $mexargs pfstools-2.2.0/doc/0002775000701400070140000000000014105165602012625 5ustar rkm38rkm38pfstools-2.2.0/doc/faq.txt0000664000701400070140000001610514105165602014136 0ustar rkm38rkm38This is a list of frequency asked questions for the pfstools package. ---------------------------------------------------------------------- General questions ---------------------------------------------------------------------- 1. What are pfstools? pfstools package is a set of command line (and one GUI) programs for reading, writing, manipulating and viewing high-dynamic range (HDR) images and video frames. All programs in the package exchange data using unix pipes and a simple generic HDR image format (pfs). The concept of the pfstools is similar to netpbm package for low-dynamic range images. 2. What are the major features? - pfstools try to handle properly colorimetry of images, not neglecting physical meaning of color data. - The filters can work on a single images as well as on a sequences of frames. - It includes a set of loaders and savers for most of the popular HDR file formats. There is no need to write one's own loader for Radiance's RGBE or link with several libraries to load OpenEXR images. - Because of its modular architecture, owning to use of UNIX pipes, the suite of tools is quite flexible. For example, to load an HDR image, tone map it, apply gamma correction and save it as a jpeg file, one can issue: pfsin memorial.hdr | pfstmo_drago03 | pfsgamma 2.2 | pfsout memorial.jpeg It's very easy to exchange one piece of processing to another. - pfs files can contain not only color information but also additional channels, like depth map, flow field or alpha channel. - Integration with GNU Octave and matlab. It includes functions to read and write HDR frame from/to Octave/matlab. This way, it's easy to write one's one tone mapping operator using a high level math language. - It includes a convenient viewer for HDR images and any data that fits into pfs files. 3. What is the license? Everything is under some form of the GNU license. The library for reading and writing of PFS streams is under The GNU Lesser General Public License (LGPL), all software, including programs for reading, writing and modifying images, are under The GNU General Public License (GPL), the specification of the PFS format is under GNU Free Documentation License (FDL). For more information on GNU licensing, see http://www.gnu.org/licenses/ . 4. Are there any alternative tools/formats for processing HDR images and video sequences? We will try to keep the updated list on the pfstools web page: http://www.mpi-sb.mpg.de/resources/pfstools/ 5. What platforms does it work on? pfstools is developed under Debian (sarge) on Intel based platform. It has also been successfully compiled under cygwin on Windows and Mac OS X (except pfsview, which requires qt).It should compile on any platform which is supported by automake/autoconf and which has required libraries (see dependencies in README file). ---------------------------------------------------------------------- PFS file format ---------------------------------------------------------------------- 1. Why pfs format does not employ any compression? pfs stream is not meant to be stored on a disk or transferred via slow networks. pfs stream should be passed from one application to another, which should involve only memory-to-memory copy and minimum amount of processing. pfs format was designed to be easy to read and at the same time generic enough to hold variety of data. 2. Why are the data stored in XYZ color space? It would be so much easier to have RGB data. For low-dynamic range data that can be displayed on the screen, RGB space may in fact seem to be more appropriate (however it does not have any physical meaning unless additional information is included). However, the human eye can see color gamut much broader than the one that can be shown on an LCD or CRT display. If such broad range of colors were to be described as RGB channels, negative values of color would have to be used. Negative values in color channels would complicate image processing much more than simple linear transform needed to convert between XYZ and any kind of RGB space. Additionally, XYZ color space (illuminant D65, normal observer) has precisely defined spectral responses and thus can accurately describe colorimetric data. ---------------------------------------------------------------------- PFS library ---------------------------------------------------------------------- 1. What functionality does the PFS library offer? * Reading and writing of pfs stream (a sequence of frames) * Color space transformations * Parsing of command line options 2. What is the programming language of the API? C++. 3. Do I have to use pfs library to read / write pfs files? No, you are welcome to write your own reader, writer, or library that can handle pfs files, as long as it complies with the specification of the pfs format. See ./doc/pfs_format_spec.pdf 4. Why does the pfs library use C style FILE instead of C++ iostreams? pfs stream is mixed text and binary format. Unfortunately C++ iostreams do not handle binary data very well. We also found many compatibilty problems on failed attempt to use iostream in the pfs library. Besides, there seems to be more libraries that use FILEs from stdio than iostreams. You can use __gnu_css::stdio_filebuf to integrate your iostream based program with the pfs library. 5. Where can I find documentation for PFS library API? Each header file of the library has Doxygen documentation. See also pfstools web page: http://www.mpi-sb.mpg.de/resources/pfstools/ 6. How to link my program with the PFS library? The best is to use autoconf + pkgconfig and include the following lines in autoconf.ac file: AC_MSG_CHECKING([for pfs library]) PKG_CHECK_MODULES(PFS, pfs >= 1.2,, AC_MSG_ERROR(pfstools package required. Download it from http://pfstools.sourceforge.net/)) AC_SUBST(PFS_CFLAGS) AC_SUBST(PFS_LIBS) than in Makefile.am: LIBS += $(PFS_LIBS) INCLUDES += $(PFS_CFLAGS) ---------------------------------------------------------------------- PFS tools ---------------------------------------------------------------------- 1. Where can I find documentation for programs in the pfstools package? Each program has its manual page with documentation (man ). 2. What is typical usage pattern for pfstools programs? To read an HDR image, tone map it, and then save to low-dynamic range file (.png), you can issue: pfsin memorial.hdr | pfstmo_drago03 | pfsout memorial.png 3. Can pfstools programs work with multiple frames? Yes. See man page for pfsinppm. Example: pfsinexr frame%04d.exr --frames 0:100 | pfstmo_drago03 | pfsoutppm tm_frame%04d.ppm To tone map frames frame0000.exr, frame0001.exr, ... frame0100.exr and save resulting frames in tm_frame0000.ppm, ..., tm_frame0100.ppm 4. What is the naming convention for pfstools programs? All programs in pfstools package are named to facilitate a -completion in a UNIX shell. Therefore the names of programs always start with 'pfs' prefix. Then follows a name of the program or name of the program group followed by the actual name of the program. Some example of groups are: pfsin* for reading files, pfsout* for writing files, and pfstmo* for tone mapping. pfstools-2.2.0/doc/data_model.xmi0000664000701400070140000065066614105165602015456 0ustar rkm38rkm38 umbrello uml modeller http://uml.sf.net 1.2.0 UnicodeUTF8
pfstools-2.2.0/doc/data_model.png0000664000701400070140000001134014105165602015421 0ustar rkm38rkm38‰PNG  IHDRÒ3å}²§IDATxœíÝ{TWâðžY¤ å("?á(êQé–X_˜âG_øÖÚ.Z_? µžÚªˆX]=J[×¢žÝStÑÚJw«¬-¢@ÔR`yÔjA¶ü„À¨< „ÌïÔÉs23É|?‡?`rçÎÍÜ™¯7w2Ž@Ix@Éùw)°ÝóC/p ÷»ÃzÙBˆ2åV˜•@Äv ôƒ^àëî5Ké+eÃvø± À(Ä.€ñ‘€óØ-¢‘¼bGõBcckrrú•+7jk›”Jâî>rêT‘蹄„h&Û*T§’'s‘ì¹u«æÔ©,±¸¼®®Y¡è÷ðp ð‹Šš³í¦¥~´{ëVÍôék¯^­øÇ?öþö[ö;gbcþ´{÷)uüÊ<¥2oÐÛ-²r 'Ÿ{n}~þ­'¶¶µe64œÿàƒUEE’ØØ¿°Ý4°`ÃÇn\ÜÑÖÖÎ'¶øÙÙÙúùݼy©——;Ã`QMÍýääôñã=óóSBCŸ <=Ÿ‰]<ÙÞžòc"€NÃÇnii!$8xŠz‰‹‹Ó/¿œS¯Ôã\Õ˜Wõ§ú—ÌÌ¢Y³b#mmCUÅ::ºwìøÌß?ÚÑ1ÌÕõåÐÐw¯_ÿAs‹§OgïÚ•êë»ÊÞ~§çâ+öVVÖ©_UÕ|÷nýªUûÜÜ^qu}ùÍ7ÿÜÒÒÑÔÔööÛG<<:;G._žÔÞÞEÇ>±}‚AMhß±:»ÒšÄÇ/±µµINNïèèV/T*•‹ï Ãô¬„c›Wb(¯½ÈåýçÏ_Ss_.WTW׿óÎÑQ£\yG] $d!äìÙÜGÚ·‘˜¸Z(tHJJKK»¢šØ*+«Z¿>yîÜxU+^$„ìßÿùÇ¿)ýÕK—î¡áÍñí;VgWZ“)S|¶lYV[Û$m¹zµB&“·´t¤¦^*,ü©¯O¡g% Û¼êë0ü%µ”” W®Ü¸}ûÿáqìªNuõyŽ3ŸèàþÅ.Õ‰3ŸIèà1>Åî œùf…^ÞðèX6ú¼ÅMq4B/pþc?ž¬:Âôg=Y‘Áq–Õ_CG/p PÀ*>M2hžö„ú4ƧZ³B/ïñ)vUMb‘èà1þÅ®šžÃ.0+ôðcWç9 €O(ÿ+0Ä.£»ŒBì0 ± À(~ÜÌý å|¸ó½À)ÜïëÅ›ƒ-z ¬»Ô,¥;¬&…Ø`b€Qˆ]F!v…Ø`b€Qˆ]Fá.5nàÃýQèNá~wX/{ŸŸœxœ€ŽàL2ð•RɯÁ7!sy ±ËcH^6 vù ÉË" uù ±Ë{H^f!vÉË uy ± „$/³¹ü†Ø…ǼŒ@ì‚$/0Ôå=Ä.< É `fˆ]Ék>êb†‡ä0Ä.P@òÒC] „ vùéÕW_Õ«’—Ff®¾}ÈŽí£zzz222õ]A•¼£1Èà>KƒØå—yóæ¼’×t†ì@cú, &ø¥¢¢¢¢¢ÂàÕ0ÛÀ #û,bôƒä5>+ÀÓ» 7$¯¹0b ä0b „äÕ†º0Ä.É `Ä.É«†º@± ÆBòjÌj%> Ôü“†ï‡"_†eÂn¡¿€c»`2$ï Ø! &Àd˜m0bè€äUÃPtAìM¼úAìÐC]ÐbèÃó/2ôƒØZñ`L= È|O3 f uÁˆ]0F’×|OÓ·fd.“ `NæŸm0ß“ÇðL30Ä.˜™u_aÃP ‡Øó³îä0ba•É‹¡.± L±²äE悱»À +K^£ vYÖ‘¼ê‚ »À8ëH^cá¡>ÀšŒæ{òeÍê‚i»ÀKÌ/Kl3p &€=˜m^Bì«,+y1Ô: vm–•¼&CìXDòb¨ 4Aì7XDòб œÁåäÅPèƒØ.áfò"sÍ/((ˆí&0± ÃÍäå«yÂÛÎ;ëëë !B¡"•JwîÜÉv£Ì± ÜéäåØP·§§ç‹/¾0dzãX²aÉDâàà ‘H6nÜÂv£Ìw©Wq$ï8ÒŒÇÔ·,[͇Ž=zñâśŋoݺ•í™F»@M `ó‡õp¡CXÙÞ²³³333>lgg—œœœ™™™=z4ûnæ<9´Ræ±ÝˆØnÙ}÷Ýw©©©¾¾¾r¹|æÌ™'OžL]° ²5ƒív™“@„ØÖ>|Xõ‹\.'„L˜0áà½{¬¶ˆ ˜dö•••±Ýæ v ˆ<øìf…I0€öÄQÒ:|ëVÍ©SYbqy]]³BÑïááà5'&f![ëÃäQjÄ.@óUÜf:ˆN<øE@€ß‰[gÏžÖÑÑýÕW×öìIËÎ.¥1vx \8iA;ÆŽR£a’8§¦æ~rrúøñžùù)¡¡Ï …žžÏÄÆ. žloX<Ä.Ðïôéì]»R}}WÙÛ/ðô\¼bÅÞÊÊ:ÍÍÍí±±7n™P6o^|JÊ;»PõgÃãÇ3úûÞÿuW×êUìííÄâOäò«ê%RióÚµ‡ÆŽ]jo¿`̘%«W¬­mR¿ªš„­­mЉ9âí½ÜÑ1lÆŒubq¹fÍ’ê?µ7^³¤ÎMB::ºwìøÌß?ÚÑ1ÌÕõåÐÐw¯_ÿahm™™E³fÅ:;GêÜ·ª;&Ô¿€qL•½h9JƒØš}óÍ¿W­Úçää¸iSÔš5‘~~c½½ŸuqyI] ¥¥ƒ2v¬;U ÞÞÏVW×74<ðóKUFUÉÈ‘Îê%Ï<ãByðà!Õ*ª±¤öw:¯Ý M46¶B´Û¦¥¦@ìÍââŽööö œ4l77ç––Žîî^Í+fš,x¾ºº>7·LËwÅÜÝG65µuvv«Ò–ÒÙÙMñðp3kã âââÔÞÞ%•6Ošämzm@#ÓRSà’ÐL5L˜>}U¹s§BŠ‹ïPˆ_bkk“œœÞÑÑ­^¨T*/Þ-†©þŒˆ&„<™1P}´ˆ0à!ª3J¡è׿ñ™9s !$+«dÐòâb -õƒÑL?JMØš­Xñ"!dÿþÏ>üM¡è¯¨¨^ºtf„„hûÍ›SŠŠ~–Ë?þøË† i˜2ÅgË–eµµM"Ñ–«W+d2yKKGjê¥ÂŸúúª2~¸ÖÃÃmÛ¶?üP­Pô—•UmÛvÂÃÃmïÞµú75$d!äìÙ\õj7Hbâj¡Ð!))--íJ{{—L&/+«Z¿>yîÜx£ëZ˜~”šßdjF}“A&“'%¥}ù¥øþýVBˆ¯ï˜„„èuëk.-­Ü»÷tQÑϽ½ò)SÆÇÆ.Ú´é77çööKêzJJîœ9“}ýúM©´Y¡è÷ô<9*jÎÚµ¿OÀÕÕ5'%¥åä”¶´t¸» Ú·o§–æ Z(•6oÜøqAÁí¾>…««S{û%×þM†¡ %’Ú”” ùù·¤Ò_e2¹³ó¦Nõ‰Ž]¤e>® ßd0½21çQjT›Dˆ] Fë  ÅÝ»õ“'G‡…ýwnîÇ lÎb v¹„¶£T Â$°@( ‹Ž>PYY×ß?pçNm|ü§ö‰‰«ÙnÀæ;JñM`ÁÛÂÃßkjjsw9gδÂÂcAA“ÙnÀæ;J1ÉÔ¸úq/0É`•0ÉÀ0ŒvšÖÛsÀ8˜Û­¬ûãÇa’Á*a’€aˆ]F!v…Ø`b€Qˆ]F!v…Ø`b€Q¸9¨áæ`ÖñðôäÁQ‡›ƒA+ë¾M“ã"¶[Àë>êps0ûŒBì0 ± À(Ä.£»ŒBì0 ± À(Ü¥Ôxp¿×ñðôäÁQ÷ÿ÷ÛŸ«™XIEND®B`‚pfstools-2.2.0/doc/pfs_format_spec.tex0000664000701400070140000003516314105165602016527 0ustar rkm38rkm38\documentclass[a4paper,12pt,english]{article} \usepackage{babel} \usepackage[]{graphicx} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% LyX specific LaTeX commands. %% Because html converters don't know tabularnewline \providecommand{\tabularnewline}{\\} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Textclass specific LaTeX commands. \newenvironment{lyxcode} {\begin{list}{}{ \setlength{\rightmargin}{\leftmargin} \setlength{\listparindent}{0pt}% needed for AMS classes \raggedright \setlength{\itemsep}{0pt} \setlength{\parsep}{0pt} \normalfont\ttfamily}% \item[]} {\end{list}} \newenvironment{lyxlist}[1] {\begin{list}{} {\settowidth{\labelwidth}{#1} \setlength{\leftmargin}{\labelwidth} \addtolength{\leftmargin}{\labelsep} \renewcommand{\makelabel}[1]{##1\hfil}}} {\end{list}} \begin{document} \title{Specification of the PFS File Format\\version 1.5} \maketitle \section{Introduction} This document contains a detailed specification of the pfs file format. PFS file format is intended to store in particular high-dynamic range images, but it is also flexible enough to store additional data, like depth map or motion flow. To learn how PFS format can be useful and why it is different from existing image formats, see a list of Frequently Asked Questions of the PFS tools package. Information in this section should be sufficient to implement pfs compatible reader or writer. \section{Copyright} Copyright (c) 2003 Rafal Mantiuk and Grzegorz Krawczyk Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.2 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. A copy of the license is included in the section entitled "GNU Free Documentation License". \section{Change History} \begin{itemize} \item 1.0 (15.12.2004 RM) --- Original version \item 1.1 (14.02.2005 RM) --- Colon ':' is no longer allowed in the name of a tag; Added a conceptual UML data-model \item 1.2 (16.03.2006 RM) --- Fixed typos (thanks to grendelkhan) \item 1.3 (16.08.2006 RM) --- Added a list of registered channel names; added comments on endianness) \item 1.4 (28.06.2007 RM) --- Fixed column-/row-major ambiguity (thanks to Matt) \item 1.5 (06.08.2007 RM) --- Specified maximum string lengths and valid value ranges. Added suggestion to prepend custom channel names with ''x''. (thanks to Martin) \item 1.6 (03.10.2008 RM) --- Added a new tag 'BITDEPTH' \end{itemize} \section{General Structure} A pfs-stream is a byte-stream that contains one or more frames. Each frame is stored in the pfs-stream right after another frame, without any markers or separators between them. End of file indicates that there are no more frames in the pfs-stream. A conceptual data model of the pfs stream is shown in Figure~\ref{cap:data-model}. A pfs-stream can contain any number of frames, which include any number of channels. Both frame and channel has an associated tag-container, which can contain any number of tags (name=value pairs). \begin{figure} \centering \includegraphics[width=\textwidth]{data_model.png} \caption{A conceptual UML data model of the pfs-stream} \label{cap:data-model} \end{figure} A structure of a single frame is shown in Table~\ref{cap:pfs-frame}. A frame encoded in the pfs format consists of a text header followed by binary data. The header contains information on frame size, number of channels and tags. Data items in the header are separated by end of line (EOL) characters. Note that only a unix-type EOL character is allowed (a single character of the ASCII code 10). MSDOS-type EOL is not allowed. To avoid portability problems, use C string ``{\tt\textbackslash{}x0a}'' instead of ``{\tt\textbackslash{}n}''. Note that the header ends with a {\tt ENDH} string, which is not followed by an EOL character. This prevents C formated IO functions, like {\tt fscanf}, from reading both the EOL character and first bytes of binary data that happen to have the values of white spaces. The binary data part of a frame follows text header and holds a 2D array of floating points for each channel. The structure of a channel is described in the next section. % \begin{table} \begin{tabular}{|l|l|p{5cm}|} \hline Data& Type& Description\tabularnewline \hline \hline \textbf{PFS1\P}& & Constant identifier\tabularnewline \hline width height\P& \emph{int int}& Width and height of each channel (1--65535) \tabularnewline \hline channelCount\P& \emph{int}& Number of channels (1--1024)\tabularnewline \hline frameTagCount\P& \emph{int}& Number of tags associated with the frame (0--1024)\tabularnewline \hline \emph{for i=1:frameTagCount}& & \tabularnewline \hline ~~tagName\textbf{=}tagValue\P& \emph{string string}& An i-th tag: name=value pair (max length of \texttt{strcat(string, string)} - 1023)\tabularnewline \hline \emph{for i=1:channelCount}& & \tabularnewline \hline ~~channelName\P& \emph{string}& Name of the i-th channel (max string length -- 32)\tabularnewline \hline ~~channelTagCount\P& \emph{int}& Number of tags associated with i-th channel (0--1024)\tabularnewline \hline ~~\emph{for j=1:channelTagCount}& & \tabularnewline \hline ~~~~tagName\textbf{=}tagValue\P& \emph{string string}& An i-th tag: name=value pair (max length of \texttt{strcat(string, string)} - 1023)\tabularnewline \hline \textbf{ENDH}& & Signalizes the end of the header\tabularnewline \hline \emph{for i=1:channelCount}& & \tabularnewline \hline ~~channelData& \emph{bin-float}{[}w{]}{[}h{]}& Row-major array of 32-bit floating points\tabularnewline \hline \end{tabular} \caption{\label{cap:pfs-frame}A structure of pfs-frame. Bold font denotes literal strings. '\P'~is a unix-type end of line character (ASCII code 10). Types: \emph{int} -- integer value given as string; \emph{string} -- a string of one or more characters (1-byte ASCII); \emph{bin-float}{[}w{]}{[}h{]} -- a row major array of 32-bit floating point numbers in binary format. The range of valid values and maximum string lengths are given in the parenthesis in the ``description'' column.} \end{table} \section{Channels} Channels are two-dimensional arrays of floating point numbers, which can contain anything from color data to motion flow and depth (z-buffer) information. Channels are written in a stream in the same order as they are listed in the header. Note that no assumption can be made on the order of channels --- it may differ from file to file and some applications may even reverse that order. For the sake of performance, channel data are encoded in binary format. Each cell (pixel) of an array should be encoded as a IEEE Standard 754 Floating Point Number. Fortunately, this representation is used by CPU for most architectures, including Intel-based PC's, so it is enough to store the values in memory as a C 'float' type and then write them to an IO stream. An array should be encoded in a stream in a row major order --- all cells of the first row are followed by the cells of the second row and so on. The encoding starts from the top left corner. The bytes should be encoded in the little-endian order (LSB), which is appropriate for the x86 platform\footnote{Current implementation of the pfs library does not handle big-endian system, so the pfs files generated on x86 and powerPC platforms are not compatible}. This version of the pfs format specification defines the following channels: \begin{description} \item {\bf X} --- X color component of the CIE XYZ color space. See comments below. \item {\bf Y} --- Y color component of the CIE XYZ color space. See comments below. \item {\bf Z} --- Z color component of the CIE XYZ color space. See comments below. \item {\bf DEPTH} --- Depth channel. Format of the depth information is currently not standardized. \item {\bf ALPHA} --- Alpha channel that encodes transparency. The values should be in the range from 0 to 1. \end{description} If other information than color, depth and alpha needs to be stored in a channel, a custom name starting with a lower-case ''x'' letter should be used. If you think that a particular channel name should be registered in this document, feel free to suggest it on the {\tt pfstools@googlegroups.com} discussion group. Color information must be represented in CIE XYZ ($2^\circ$ standard observer) color space. Depending on the value of the LUMINANCE tag, color data are linear (RELATIVE -- linearly related to luminance), linear and calibrated (ABSOLUTE -- the values of Y channel represent luminance in $cd/m^2$), or non-linear (DISPLAY -- gamma-corrected). The preferred representation is a 'linear and calibrated'. 'non-linear' is only a temporary representation used before data is written to low dynamic range image files (PNG, JPG) or displayed. For more information refer to the next section --- \ref{sec:tags}. The channels must be named using upper case letters: 'X', 'Y' and 'Z'. For color images, all three X, Y and Z channels must be given. It is enough to include 'Y' channel for gray-level images. \section{Tags} \label{sec:tags} pfs format allows for storing tags, which are 'name=value' pairs. Tags are robust mechanism for specifying additional information within a pfs-stream. Both the name and value should be a text string, i.e. if a floating point number is to be stored, it should be converted into a text string. The name must not contain '=' and ':' character (some programs use notation 'channel\_name:tag\_name' to assign a tag to the channel). Spaces in front of and after '=' are allowed, although they will not be skipped and they will be interpreted as a part of name or value strings. Tags can be associated with a frame or separate channel, depending how they are placed in the text header of the pfs frame (see Table~\ref{cap:pfs-frame}). Each tag associated with a single frame or channel must have an unique name. There is no limit to the number of tags. In general, tags can be used to store any user defined data, but there are also several tags that are reserved and are part of the pfs format. Those tags let precisely define content of the pfs stream. The reserved tags are listed below: \begin{description} \item LUMINANCE specifies a photometric content of the XYZ color channels or Y luminance channel alone. LUMINANCE tag distinguishes between different methods of storing lumiance data. This tag must be associated with a frame and the frame must contain either all XYZ channels or Y channel. Possible values are: ABSOLUTE, RELATIVE, and DISPLAY. \begin{description} \item ABSOLUTE Set the LUMINANCE tag to this value if the high-dynamic range data is calibrated. Y channel should contain absolute luminance values given in $cd/m^2$. Note that luminance values must be $\geq0$ for calibrated content. \item RELATIVE High-dynamic range data is of RELATIVE LUMINANCE if it is not calibrated, that is Y channel data is proportional to luminance, but nothing is known about absolute value of luminance. This is the case of most of the high-dynamic range images available on Internet. \item DISPLAY If the data comes from low-dynamic range file and no colorimetric information is provided in ICC profile, the data is simply given in pixel values of an unknown display device. This is the worst case, but also most common as most of the low-dynamic range formats (jpeg, png, tiff) do not contain any data that can be used to recover luminance values. The values of the Y channel can range from $0$ to $1$ and are already gamma corrected. \end{description} If the LUMINANCE tag is not specified in the pfs-stream, RELATIVE is assumed. Example: LUMINANCE=ABSOLUTE \item VISUAL\_CONTENT tag specifies visual content of the XYZ color channels or Y luminance channel alone. If an image was captured as high-dynamic range photograph, it has one-to-one correspondence with the real word, therefore its VISUAL\_CONTENT is MEASUREMENT. However, if the same image has been tone mapped (or gamut mapped) it still resembles appearance of the real word scene, but it has no longer one-to-one correspondence. In this case its VISUAL\_CONTENT is RENDERING. This tag is typically set to rendering after tone mapping of high-dynamic range images. This tag must be associated with a frame and the frame must contain either all XYZ channels or Y channel. \begin{description} \item MEASUREMENT Set VISUAL\_CONTENT tag to MEASUREMENT if the image has one-to-one correspondence with the real word, i.e. it contains measured data. \item RENDERING Set VISUAL\_CONTENT to RENDERING if the image has the same appearance as the real world scene, but there is no photometric correspondence. \end{description} If the tag is not specified in the pfs-stream, MEASUREMENT is assumed. If LUMINANCE tag is set to DISPLAY, the VISUAL\_CONTENT is assumed to be RENDERING. Example: VISUAL\_CONTENT=RENDERING \item WHITE\_Y defines the value of channel Y that is perceived as reference white. The value should be given in units of an Y channel. If LUMINANCE=ABSOLUTE, the white point is given in $cd/m^2$. The value of this tag should be a single floating point number in a text format. Example: WHITE\_Y=100 \item WHITE\_x, WHITE\_y tags define relative coordinates of color that is perceived as reference white. The value of those tags must be a floating point number in a text format. The value is given as a relative colorimatric coordinates of the CIE XYZ space ($2^\circ$ standard observer) $y=Y/(X+Y+Z)$ and $x=X/(X+Y+Z)$. If the tag is not specified, D65 white point is assumed: $x=0.3127\:y=0.3290$ Example: WHITE\_x=0.3457~~WHITE\_y=0.3585 \item FILE\_NAME contains a string with the name of a source file. This can be used to locate the origin of a frame. Example: FILE\_NAME=my\_sequence\_frame\_0000.hdr \item BITDEPTH specifies the number of bits, that are required to store a single channel in a display-referred image (LUMINANCE=DISPLAY) without quality loss. The number should be from 1 to 32. Example: BITDEPTH=16 \end{description} \section{Discussion} The structure of pfs files is a compromise between simplicity and robustness and (as well) between portability and performance. Therefore a small header is stored in text format as this is the easiest to decode on different platforms. Storing image data as text would result in too much overhead and thus binary format is used. Since parsing a file is typically more difficult to implement than writing it, pfs format tries to make parsing files as easy as possible. This is why sizes of all variable length structures are given first and then actual data follows. \end{document} pfstools-2.2.0/Doxyfile0000664000701400070140000013104214105165602013565 0ustar rkm38rkm38# Doxyfile 1.3.7 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project # # All text after a hash (#) is considered a comment and will be ignored # The format is: # TAG = value [value, ...] # For lists items can also be appended using: # TAG += value [value, ...] # Values that contain spaces should be placed between quotes (" ") #--------------------------------------------------------------------------- # Project related configuration options #--------------------------------------------------------------------------- # The PROJECT_NAME tag is a single word (or a sequence of words surrounded # by quotes) that should identify the project. PROJECT_NAME = "Portable Floating-point Streams (pfstools)" # The PROJECT_NUMBER tag can be used to enter a project or revision number. # This could be handy for archiving the generated documentation or # if some version control system is used. PROJECT_NUMBER = # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) # base path where the generated documentation will be put. # If a relative path is entered, it will be relative to the location # where doxygen was started. If left blank the current directory will be used. OUTPUT_DIRECTORY = ./doxygen # If the CREATE_SUBDIRS tag is set to YES, then doxygen will create # 2 levels of 10 sub-directories under the output directory of each output # format and will distribute the generated files over these directories. # Enabling this option can be useful when feeding doxygen a huge amount of source # files, where putting all generated files in the same directory would otherwise # cause performance problems for the file system. CREATE_SUBDIRS = NO # The OUTPUT_LANGUAGE tag is used to specify the language in which all # documentation generated by doxygen is written. Doxygen will use this # information to generate all constant output in the proper language. # The default language is English, other supported languages are: # Brazilian, Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, Dutch, # Finnish, French, German, Greek, Hungarian, Italian, Japanese, Japanese-en # (Japanese with English messages), Korean, Korean-en, Norwegian, Polish, Portuguese, # Romanian, Russian, Serbian, Slovak, Slovene, Spanish, Swedish, and Ukrainian. OUTPUT_LANGUAGE = English # If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will # include brief member descriptions after the members that are listed in # the file and class documentation (similar to JavaDoc). # Set to NO to disable this. BRIEF_MEMBER_DESC = YES # If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend # the brief description of a member or function before the detailed description. # Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the # brief descriptions will be completely suppressed. REPEAT_BRIEF = YES # This tag implements a quasi-intelligent brief description abbreviator # that is used to form the text in various listings. Each string # in this list, if found as the leading text of the brief description, will be # stripped from the text and the result after processing the whole list, is used # as the annotated text. Otherwise, the brief description is used as-is. If left # blank, the following values are used ("$name" is automatically replaced with the # name of the entity): "The $name class" "The $name widget" "The $name file" # "is" "provides" "specifies" "contains" "represents" "a" "an" "the" ABBREVIATE_BRIEF = # If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then # Doxygen will generate a detailed section even if there is only a brief # description. ALWAYS_DETAILED_SEC = NO # If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all inherited # members of a class in the documentation of that class as if those members were # ordinary class members. Constructors, destructors and assignment operators of # the base classes will not be shown. INLINE_INHERITED_MEMB = NO # If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full # path before files name in the file list and in the header files. If set # to NO the shortest path that makes the file name unique will be used. FULL_PATH_NAMES = NO # If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag # can be used to strip a user-defined part of the path. Stripping is # only done if one of the specified strings matches the left-hand part of # the path. The tag can be used to show relative paths in the file list. # If left blank the directory from which doxygen is run is used as the # path to strip. STRIP_FROM_PATH = # The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of # the path mentioned in the documentation of a class, which tells # the reader which header file to include in order to use a class. # If left blank only the name of the header file containing the class # definition is used. Otherwise one should specify the include paths that # are normally passed to the compiler using the -I flag. STRIP_FROM_INC_PATH = # If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter # (but less readable) file names. This can be useful is your file systems # doesn't support long names like on DOS, Mac, or CD-ROM. SHORT_NAMES = NO # If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen # will interpret the first line (until the first dot) of a JavaDoc-style # comment as the brief description. If set to NO, the JavaDoc # comments will behave just like the Qt-style comments (thus requiring an # explicit @brief command for a brief description. JAVADOC_AUTOBRIEF = YES # The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen # treat a multi-line C++ special comment block (i.e. a block of //! or /// # comments) as a brief description. This used to be the default behaviour. # The new default is to treat a multi-line C++ comment block as a detailed # description. Set this tag to YES if you prefer the old behaviour instead. MULTILINE_CPP_IS_BRIEF = NO # If the DETAILS_AT_TOP tag is set to YES then Doxygen # will output the detailed description near the top, like JavaDoc. # If set to NO, the detailed description appears after the member # documentation. DETAILS_AT_TOP = NO # If the INHERIT_DOCS tag is set to YES (the default) then an undocumented # member inherits the documentation from any documented member that it # re-implements. INHERIT_DOCS = YES # If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC # tag is set to YES, then doxygen will reuse the documentation of the first # member in the group (if any) for the other members of the group. By default # all members of a group must be documented explicitly. DISTRIBUTE_GROUP_DOC = NO # The TAB_SIZE tag can be used to set the number of spaces in a tab. # Doxygen uses this value to replace tabs by spaces in code fragments. TAB_SIZE = 2 # This tag can be used to specify a number of aliases that acts # as commands in the documentation. An alias has the form "name=value". # For example adding "sideeffect=\par Side Effects:\n" will allow you to # put the command \sideeffect (or @sideeffect) in the documentation, which # will result in a user-defined paragraph with heading "Side Effects:". # You can put \n's in the value part of an alias to insert newlines. ALIASES = # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources # only. Doxygen will then generate output that is more tailored for C. # For instance, some of the names that are used will be different. The list # of all members will be omitted, etc. OPTIMIZE_OUTPUT_FOR_C = NO # Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java sources # only. Doxygen will then generate output that is more tailored for Java. # For instance, namespaces will be presented as packages, qualified scopes # will look different, etc. OPTIMIZE_OUTPUT_JAVA = NO # Set the SUBGROUPING tag to YES (the default) to allow class member groups of # the same type (for instance a group of public functions) to be put as a # subgroup of that type (e.g. under the Public Functions section). Set it to # NO to prevent subgrouping. Alternatively, this can be done per class using # the \nosubgrouping command. SUBGROUPING = YES #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- # If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in # documentation are documented, even if no documentation was available. # Private class members and static file members will be hidden unless # the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES EXTRACT_ALL = NO # If the EXTRACT_PRIVATE tag is set to YES all private members of a class # will be included in the documentation. EXTRACT_PRIVATE = NO # If the EXTRACT_STATIC tag is set to YES all static members of a file # will be included in the documentation. EXTRACT_STATIC = NO # If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) # defined locally in source files will be included in the documentation. # If set to NO only classes defined in header files are included. EXTRACT_LOCAL_CLASSES = NO # This flag is only useful for Objective-C code. When set to YES local # methods, which are defined in the implementation section but not in # the interface are included in the documentation. # If set to NO (the default) only methods in the interface are included. EXTRACT_LOCAL_METHODS = NO # If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all # undocumented members of documented classes, files or namespaces. # If set to NO (the default) these members will be included in the # various overviews, but no documentation section is generated. # This option has no effect if EXTRACT_ALL is enabled. HIDE_UNDOC_MEMBERS = YES # If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all # undocumented classes that are normally visible in the class hierarchy. # If set to NO (the default) these classes will be included in the various # overviews. This option has no effect if EXTRACT_ALL is enabled. HIDE_UNDOC_CLASSES = YES # If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all # friend (class|struct|union) declarations. # If set to NO (the default) these declarations will be included in the # documentation. HIDE_FRIEND_COMPOUNDS = NO # If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any # documentation blocks found inside the body of a function. # If set to NO (the default) these blocks will be appended to the # function's detailed documentation block. HIDE_IN_BODY_DOCS = NO # The INTERNAL_DOCS tag determines if documentation # that is typed after a \internal command is included. If the tag is set # to NO (the default) then the documentation will be excluded. # Set it to YES to include the internal documentation. INTERNAL_DOCS = NO # If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate # file names in lower-case letters. If set to YES upper-case letters are also # allowed. This is useful if you have classes or files whose names only differ # in case and if your file system supports case sensitive file names. Windows # users are advised to set this option to NO. CASE_SENSE_NAMES = YES # If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen # will show members with their full class and namespace scopes in the # documentation. If set to YES the scope will be hidden. HIDE_SCOPE_NAMES = NO # If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen # will put a list of the files that are included by a file in the documentation # of that file. SHOW_INCLUDE_FILES = YES # If the INLINE_INFO tag is set to YES (the default) then a tag [inline] # is inserted in the documentation for inline members. INLINE_INFO = YES # If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen # will sort the (detailed) documentation of file and class members # alphabetically by member name. If set to NO the members will appear in # declaration order. SORT_MEMBER_DOCS = YES # If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the # brief documentation of file, namespace and class members alphabetically # by member name. If set to NO (the default) the members will appear in # declaration order. SORT_BRIEF_DOCS = NO # If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be # sorted by fully-qualified names, including namespaces. If set to # NO (the default), the class list will be sorted only by class name, # not including the namespace part. # Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. # Note: This option applies only to the class list, not to the # alphabetical list. SORT_BY_SCOPE_NAME = NO # The GENERATE_TODOLIST tag can be used to enable (YES) or # disable (NO) the todo list. This list is created by putting \todo # commands in the documentation. GENERATE_TODOLIST = YES # The GENERATE_TESTLIST tag can be used to enable (YES) or # disable (NO) the test list. This list is created by putting \test # commands in the documentation. GENERATE_TESTLIST = YES # The GENERATE_BUGLIST tag can be used to enable (YES) or # disable (NO) the bug list. This list is created by putting \bug # commands in the documentation. GENERATE_BUGLIST = YES # The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or # disable (NO) the deprecated list. This list is created by putting # \deprecated commands in the documentation. GENERATE_DEPRECATEDLIST= YES # The ENABLED_SECTIONS tag can be used to enable conditional # documentation sections, marked by \if sectionname ... \endif. ENABLED_SECTIONS = # The MAX_INITIALIZER_LINES tag determines the maximum number of lines # the initial value of a variable or define consists of for it to appear in # the documentation. If the initializer consists of more lines than specified # here it will be hidden. Use a value of 0 to hide initializers completely. # The appearance of the initializer of individual variables and defines in the # documentation can be controlled using \showinitializer or \hideinitializer # command in the documentation regardless of this setting. MAX_INITIALIZER_LINES = 30 # Set the SHOW_USED_FILES tag to NO to disable the list of files generated # at the bottom of the documentation of classes and structs. If set to YES the # list will mention the files that were used to generate the documentation. SHOW_USED_FILES = YES #--------------------------------------------------------------------------- # configuration options related to warning and progress messages #--------------------------------------------------------------------------- # The QUIET tag can be used to turn on/off the messages that are generated # by doxygen. Possible values are YES and NO. If left blank NO is used. QUIET = YES # The WARNINGS tag can be used to turn on/off the warning messages that are # generated by doxygen. Possible values are YES and NO. If left blank # NO is used. WARNINGS = YES # If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings # for undocumented members. If EXTRACT_ALL is set to YES then this flag will # automatically be disabled. WARN_IF_UNDOCUMENTED = YES # If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for # potential errors in the documentation, such as not documenting some # parameters in a documented function, or documenting parameters that # don't exist or using markup commands wrongly. WARN_IF_DOC_ERROR = YES # The WARN_FORMAT tag determines the format of the warning messages that # doxygen can produce. The string should contain the $file, $line, and $text # tags, which will be replaced by the file and line number from which the # warning originated and the warning text. WARN_FORMAT = "$file:$line: $text" # The WARN_LOGFILE tag can be used to specify a file to which warning # and error messages should be written. If left blank the output is written # to stderr. WARN_LOGFILE = #--------------------------------------------------------------------------- # configuration options related to the input files #--------------------------------------------------------------------------- # The INPUT tag can be used to specify the files and/or directories that contain # documented source files. You may enter file names like "myfile.cpp" or # directories like "/usr/src/myproject". Separate the files or directories # with spaces. INPUT = # If the value of the INPUT tag contains directories, you can use the # FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp # and *.h) to filter out the source-files in the directories. If left # blank the following patterns are tested: # *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx *.hpp # *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm FILE_PATTERNS = pfs.h \ array2d.h # The RECURSIVE tag can be used to turn specify whether or not subdirectories # should be searched for input files as well. Possible values are YES and NO. # If left blank NO is used. RECURSIVE = YES # The EXCLUDE tag can be used to specify files and/or directories that should # excluded from the INPUT source files. This way you can easily exclude a # subdirectory from a directory tree whose root is specified with the INPUT tag. EXCLUDE = # The EXCLUDE_SYMLINKS tag can be used select whether or not files or directories # that are symbolic links (a Unix filesystem feature) are excluded from the input. EXCLUDE_SYMLINKS = NO # If the value of the INPUT tag contains directories, you can use the # EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude # certain files from those directories. EXCLUDE_PATTERNS = # The EXAMPLE_PATH tag can be used to specify one or more files or # directories that contain example code fragments that are included (see # the \include command). EXAMPLE_PATH = # If the value of the EXAMPLE_PATH tag contains directories, you can use the # EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp # and *.h) to filter out the source-files in the directories. If left # blank all files are included. EXAMPLE_PATTERNS = # If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be # searched for input files to be used with the \include or \dontinclude # commands irrespective of the value of the RECURSIVE tag. # Possible values are YES and NO. If left blank NO is used. EXAMPLE_RECURSIVE = NO # The IMAGE_PATH tag can be used to specify one or more files or # directories that contain image that are included in the documentation (see # the \image command). IMAGE_PATH = # The INPUT_FILTER tag can be used to specify a program that doxygen should # invoke to filter for each input file. Doxygen will invoke the filter program # by executing (via popen()) the command , where # is the value of the INPUT_FILTER tag, and is the name of an # input file. Doxygen will then use the output that the filter program writes # to standard output. INPUT_FILTER = # If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using # INPUT_FILTER) will be used to filter the input files when producing source # files to browse (i.e. when SOURCE_BROWSER is set to YES). FILTER_SOURCE_FILES = NO #--------------------------------------------------------------------------- # configuration options related to source browsing #--------------------------------------------------------------------------- # If the SOURCE_BROWSER tag is set to YES then a list of source files will # be generated. Documented entities will be cross-referenced with these sources. # Note: To get rid of all source code in the generated output, make sure also # VERBATIM_HEADERS is set to NO. SOURCE_BROWSER = YES # Setting the INLINE_SOURCES tag to YES will include the body # of functions and classes directly in the documentation. INLINE_SOURCES = NO # Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct # doxygen to hide any special comment blocks from generated source code # fragments. Normal C and C++ comments will always remain visible. STRIP_CODE_COMMENTS = YES # If the REFERENCED_BY_RELATION tag is set to YES (the default) # then for each documented function all documented # functions referencing it will be listed. REFERENCED_BY_RELATION = NO # If the REFERENCES_RELATION tag is set to YES (the default) # then for each documented function all documented entities # called/used by that function will be listed. REFERENCES_RELATION = NO # If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen # will generate a verbatim copy of the header file for each class for # which an include is specified. Set to NO to disable this. VERBATIM_HEADERS = YES #--------------------------------------------------------------------------- # configuration options related to the alphabetical class index #--------------------------------------------------------------------------- # If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index # of all compounds will be generated. Enable this if the project # contains a lot of classes, structs, unions or interfaces. ALPHABETICAL_INDEX = YES # If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then # the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns # in which this list will be split (can be a number in the range [1..20]) COLS_IN_ALPHA_INDEX = 5 # In case all classes in a project start with a common prefix, all # classes will be put under the same header in the alphabetical index. # The IGNORE_PREFIX tag can be used to specify one or more prefixes that # should be ignored while generating the index headers. IGNORE_PREFIX = #--------------------------------------------------------------------------- # configuration options related to the HTML output #--------------------------------------------------------------------------- # If the GENERATE_HTML tag is set to YES (the default) Doxygen will # generate HTML output. GENERATE_HTML = YES # The HTML_OUTPUT tag is used to specify where the HTML docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `html' will be used as the default path. HTML_OUTPUT = html # The HTML_FILE_EXTENSION tag can be used to specify the file extension for # each generated HTML page (for example: .htm,.php,.asp). If it is left blank # doxygen will generate files with .html extension. HTML_FILE_EXTENSION = .html # The HTML_HEADER tag can be used to specify a personal HTML header for # each generated HTML page. If it is left blank doxygen will generate a # standard header. HTML_HEADER = # The HTML_FOOTER tag can be used to specify a personal HTML footer for # each generated HTML page. If it is left blank doxygen will generate a # standard footer. HTML_FOOTER = # The HTML_STYLESHEET tag can be used to specify a user-defined cascading # style sheet that is used by each HTML page. It can be used to # fine-tune the look of the HTML output. If the tag is left blank doxygen # will generate a default style sheet. Note that doxygen will try to copy # the style sheet file to the HTML output directory, so don't put your own # stylesheet in the HTML output directory as well, or it will be erased! HTML_STYLESHEET = # If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, # files or namespaces will be aligned in HTML using tables. If set to # NO a bullet list will be used. HTML_ALIGN_MEMBERS = YES # If the GENERATE_HTMLHELP tag is set to YES, additional index files # will be generated that can be used as input for tools like the # Microsoft HTML help workshop to generate a compressed HTML help file (.chm) # of the generated HTML documentation. GENERATE_HTMLHELP = NO # If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can # be used to specify the file name of the resulting .chm file. You # can add a path in front of the file if the result should not be # written to the html output directory. CHM_FILE = # If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can # be used to specify the location (absolute path including file name) of # the HTML help compiler (hhc.exe). If non-empty doxygen will try to run # the HTML help compiler on the generated index.hhp. HHC_LOCATION = # If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag # controls if a separate .chi index file is generated (YES) or that # it should be included in the master .chm file (NO). GENERATE_CHI = NO # If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag # controls whether a binary table of contents is generated (YES) or a # normal table of contents (NO) in the .chm file. BINARY_TOC = NO # The TOC_EXPAND flag can be set to YES to add extra items for group members # to the contents of the HTML help documentation and to the tree view. TOC_EXPAND = NO # The DISABLE_INDEX tag can be used to turn on/off the condensed index at # top of each HTML page. The value NO (the default) enables the index and # the value YES disables it. DISABLE_INDEX = NO # This tag can be used to set the number of enum values (range [1..20]) # that doxygen will group on one line in the generated HTML documentation. ENUM_VALUES_PER_LINE = 4 # If the GENERATE_TREEVIEW tag is set to YES, a side panel will be # generated containing a tree-like index structure (just like the one that # is generated for HTML Help). For this to work a browser that supports # JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+, # Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are # probably better off using the HTML help feature. GENERATE_TREEVIEW = YES # If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be # used to set the initial width (in pixels) of the frame in which the tree # is shown. TREEVIEW_WIDTH = 250 #--------------------------------------------------------------------------- # configuration options related to the LaTeX output #--------------------------------------------------------------------------- # If the GENERATE_LATEX tag is set to YES (the default) Doxygen will # generate Latex output. GENERATE_LATEX = NO # The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `latex' will be used as the default path. LATEX_OUTPUT = latex # The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be # invoked. If left blank `latex' will be used as the default command name. LATEX_CMD_NAME = latex # The MAKEINDEX_CMD_NAME tag can be used to specify the command name to # generate index for LaTeX. If left blank `makeindex' will be used as the # default command name. MAKEINDEX_CMD_NAME = makeindex # If the COMPACT_LATEX tag is set to YES Doxygen generates more compact # LaTeX documents. This may be useful for small projects and may help to # save some trees in general. COMPACT_LATEX = NO # The PAPER_TYPE tag can be used to set the paper type that is used # by the printer. Possible values are: a4, a4wide, letter, legal and # executive. If left blank a4wide will be used. PAPER_TYPE = a4wide # The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX # packages that should be included in the LaTeX output. EXTRA_PACKAGES = # The LATEX_HEADER tag can be used to specify a personal LaTeX header for # the generated latex document. The header should contain everything until # the first chapter. If it is left blank doxygen will generate a # standard header. Notice: only use this tag if you know what you are doing! LATEX_HEADER = # If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated # is prepared for conversion to pdf (using ps2pdf). The pdf file will # contain links (just like the HTML output) instead of page references # This makes the output suitable for online browsing using a pdf viewer. PDF_HYPERLINKS = NO # If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of # plain latex in the generated Makefile. Set this option to YES to get a # higher quality PDF documentation. USE_PDFLATEX = NO # If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. # command to the generated LaTeX files. This will instruct LaTeX to keep # running if errors occur, instead of asking the user for help. # This option is also used when generating formulas in HTML. LATEX_BATCHMODE = NO # If LATEX_HIDE_INDICES is set to YES then doxygen will not # include the index chapters (such as File Index, Compound Index, etc.) # in the output. LATEX_HIDE_INDICES = NO #--------------------------------------------------------------------------- # configuration options related to the RTF output #--------------------------------------------------------------------------- # If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output # The RTF output is optimized for Word 97 and may not look very pretty with # other RTF readers or editors. GENERATE_RTF = NO # The RTF_OUTPUT tag is used to specify where the RTF docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `rtf' will be used as the default path. RTF_OUTPUT = rtf # If the COMPACT_RTF tag is set to YES Doxygen generates more compact # RTF documents. This may be useful for small projects and may help to # save some trees in general. COMPACT_RTF = NO # If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated # will contain hyperlink fields. The RTF file will # contain links (just like the HTML output) instead of page references. # This makes the output suitable for online browsing using WORD or other # programs which support those fields. # Note: wordpad (write) and others do not support links. RTF_HYPERLINKS = NO # Load stylesheet definitions from file. Syntax is similar to doxygen's # config file, i.e. a series of assignments. You only have to provide # replacements, missing definitions are set to their default value. RTF_STYLESHEET_FILE = # Set optional variables used in the generation of an rtf document. # Syntax is similar to doxygen's config file. RTF_EXTENSIONS_FILE = #--------------------------------------------------------------------------- # configuration options related to the man page output #--------------------------------------------------------------------------- # If the GENERATE_MAN tag is set to YES (the default) Doxygen will # generate man pages GENERATE_MAN = NO # The MAN_OUTPUT tag is used to specify where the man pages will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `man' will be used as the default path. MAN_OUTPUT = man # The MAN_EXTENSION tag determines the extension that is added to # the generated man pages (default is the subroutine's section .3) MAN_EXTENSION = .3 # If the MAN_LINKS tag is set to YES and Doxygen generates man output, # then it will generate one additional man file for each entity # documented in the real man page(s). These additional files # only source the real man page, but without them the man command # would be unable to find the correct page. The default is NO. MAN_LINKS = NO #--------------------------------------------------------------------------- # configuration options related to the XML output #--------------------------------------------------------------------------- # If the GENERATE_XML tag is set to YES Doxygen will # generate an XML file that captures the structure of # the code including all documentation. GENERATE_XML = NO # The XML_OUTPUT tag is used to specify where the XML pages will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `xml' will be used as the default path. XML_OUTPUT = xml # The XML_SCHEMA tag can be used to specify an XML schema, # which can be used by a validating XML parser to check the # syntax of the XML files. XML_SCHEMA = # The XML_DTD tag can be used to specify an XML DTD, # which can be used by a validating XML parser to check the # syntax of the XML files. XML_DTD = # If the XML_PROGRAMLISTING tag is set to YES Doxygen will # dump the program listings (including syntax highlighting # and cross-referencing information) to the XML output. Note that # enabling this will significantly increase the size of the XML output. XML_PROGRAMLISTING = YES #--------------------------------------------------------------------------- # configuration options for the AutoGen Definitions output #--------------------------------------------------------------------------- # If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will # generate an AutoGen Definitions (see autogen.sf.net) file # that captures the structure of the code including all # documentation. Note that this feature is still experimental # and incomplete at the moment. GENERATE_AUTOGEN_DEF = NO #--------------------------------------------------------------------------- # configuration options related to the Perl module output #--------------------------------------------------------------------------- # If the GENERATE_PERLMOD tag is set to YES Doxygen will # generate a Perl module file that captures the structure of # the code including all documentation. Note that this # feature is still experimental and incomplete at the # moment. GENERATE_PERLMOD = NO # If the PERLMOD_LATEX tag is set to YES Doxygen will generate # the necessary Makefile rules, Perl scripts and LaTeX code to be able # to generate PDF and DVI output from the Perl module output. PERLMOD_LATEX = NO # If the PERLMOD_PRETTY tag is set to YES the Perl module output will be # nicely formatted so it can be parsed by a human reader. This is useful # if you want to understand what is going on. On the other hand, if this # tag is set to NO the size of the Perl module output will be much smaller # and Perl will parse it just the same. PERLMOD_PRETTY = YES # The names of the make variables in the generated doxyrules.make file # are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. # This is useful so different doxyrules.make files included by the same # Makefile don't overwrite each other's variables. PERLMOD_MAKEVAR_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the preprocessor #--------------------------------------------------------------------------- # If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will # evaluate all C-preprocessor directives found in the sources and include # files. ENABLE_PREPROCESSING = YES # If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro # names in the source code. If set to NO (the default) only conditional # compilation will be performed. Macro expansion can be done in a controlled # way by setting EXPAND_ONLY_PREDEF to YES. MACRO_EXPANSION = NO # If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES # then the macro expansion is limited to the macros specified with the # PREDEFINED and EXPAND_AS_PREDEFINED tags. EXPAND_ONLY_PREDEF = NO # If the SEARCH_INCLUDES tag is set to YES (the default) the includes files # in the INCLUDE_PATH (see below) will be search if a #include is found. SEARCH_INCLUDES = YES # The INCLUDE_PATH tag can be used to specify one or more directories that # contain include files that are not input files but should be processed by # the preprocessor. INCLUDE_PATH = # You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard # patterns (like *.h and *.hpp) to filter out the header-files in the # directories. If left blank, the patterns specified with FILE_PATTERNS will # be used. INCLUDE_FILE_PATTERNS = # The PREDEFINED tag can be used to specify one or more macro names that # are defined before the preprocessor is started (similar to the -D option of # gcc). The argument of the tag is a list of macros of the form: name # or name=definition (no spaces). If the definition and the = are # omitted =1 is assumed. PREDEFINED = # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then # this tag can be used to specify a list of macro names that should be expanded. # The macro definition that is found in the sources will be used. # Use the PREDEFINED tag if you want to use a different macro definition. EXPAND_AS_DEFINED = # If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then # doxygen's preprocessor will remove all function-like macros that are alone # on a line, have an all uppercase name, and do not end with a semicolon. Such # function macros are typically used for boiler-plate code, and will confuse the # parser if not removed. SKIP_FUNCTION_MACROS = YES #--------------------------------------------------------------------------- # Configuration::additions related to external references #--------------------------------------------------------------------------- # The TAGFILES option can be used to specify one or more tagfiles. # Optionally an initial location of the external documentation # can be added for each tagfile. The format of a tag file without # this location is as follows: # TAGFILES = file1 file2 ... # Adding location for the tag files is done as follows: # TAGFILES = file1=loc1 "file2 = loc2" ... # where "loc1" and "loc2" can be relative or absolute paths or # URLs. If a location is present for each tag, the installdox tool # does not have to be run to correct the links. # Note that each tag file must have a unique name # (where the name does NOT include the path) # If a tag file is not located in the directory in which doxygen # is run, you must also specify the path to the tagfile here. TAGFILES = # When a file name is specified after GENERATE_TAGFILE, doxygen will create # a tag file that is based on the input files it reads. GENERATE_TAGFILE = # If the ALLEXTERNALS tag is set to YES all external classes will be listed # in the class index. If set to NO only the inherited external classes # will be listed. ALLEXTERNALS = NO # If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed # in the modules index. If set to NO, only the current project's groups will # be listed. EXTERNAL_GROUPS = YES # The PERL_PATH should be the absolute path and name of the perl script # interpreter (i.e. the result of `which perl'). PERL_PATH = /usr/bin/perl #--------------------------------------------------------------------------- # Configuration options related to the dot tool #--------------------------------------------------------------------------- # If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will # generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base or # super classes. Setting the tag to NO turns the diagrams off. Note that this # option is superseded by the HAVE_DOT option below. This is only a fallback. It is # recommended to install and use dot, since it yields more powerful graphs. CLASS_DIAGRAMS = YES # If set to YES, the inheritance and collaboration graphs will hide # inheritance and usage relations if the target is undocumented # or is not a class. HIDE_UNDOC_RELATIONS = YES # If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is # available from the path. This tool is part of Graphviz, a graph visualization # toolkit from AT&T and Lucent Bell Labs. The other options in this section # have no effect if this option is set to NO (the default) HAVE_DOT = NO # If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen # will generate a graph for each documented class showing the direct and # indirect inheritance relations. Setting this tag to YES will force the # the CLASS_DIAGRAMS tag to NO. CLASS_GRAPH = YES # If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen # will generate a graph for each documented class showing the direct and # indirect implementation dependencies (inheritance, containment, and # class references variables) of the class with other documented classes. COLLABORATION_GRAPH = YES # If the UML_LOOK tag is set to YES doxygen will generate inheritance and # collaboration diagrams in a style similar to the OMG's Unified Modeling # Language. UML_LOOK = NO # If set to YES, the inheritance and collaboration graphs will show the # relations between templates and their instances. TEMPLATE_RELATIONS = YES # If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT # tags are set to YES then doxygen will generate a graph for each documented # file showing the direct and indirect include dependencies of the file with # other documented files. INCLUDE_GRAPH = YES # If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and # HAVE_DOT tags are set to YES then doxygen will generate a graph for each # documented header file showing the documented files that directly or # indirectly include this file. INCLUDED_BY_GRAPH = YES # If the CALL_GRAPH and HAVE_DOT tags are set to YES then doxygen will # generate a call dependency graph for every global function or class method. # Note that enabling this option will significantly increase the time of a run. # So in most cases it will be better to enable call graphs for selected # functions only using the \callgraph command. CALL_GRAPH = NO # If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen # will graphical hierarchy of all classes instead of a textual one. GRAPHICAL_HIERARCHY = YES # The DOT_IMAGE_FORMAT tag can be used to set the image format of the images # generated by dot. Possible values are png, jpg, or gif # If left blank png will be used. DOT_IMAGE_FORMAT = png # The tag DOT_PATH can be used to specify the path where the dot tool can be # found. If left blank, it is assumed the dot tool can be found on the path. DOT_PATH = # The DOTFILE_DIRS tag can be used to specify one or more directories that # contain dot files that are included in the documentation (see the # \dotfile command). DOTFILE_DIRS = # The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the # graphs generated by dot. A depth value of 3 means that only nodes reachable # from the root by following a path via at most 3 edges will be shown. Nodes that # lay further from the root node will be omitted. Note that setting this option to # 1 or 2 may greatly reduce the computation time needed for large code bases. Also # note that a graph may be further truncated if the graph's image dimensions are # not sufficient to fit the graph (see MAX_DOT_GRAPH_WIDTH and MAX_DOT_GRAPH_HEIGHT). # If 0 is used for the depth value (the default), the graph is not depth-constrained. MAX_DOT_GRAPH_DEPTH = 0 # If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will # generate a legend page explaining the meaning of the various boxes and # arrows in the dot generated graphs. GENERATE_LEGEND = YES # If the DOT_CLEANUP tag is set to YES (the default) Doxygen will # remove the intermediate dot files that are used to generate # the various graphs. DOT_CLEANUP = YES #--------------------------------------------------------------------------- # Configuration::additions related to the search engine #--------------------------------------------------------------------------- # The SEARCHENGINE tag specifies whether or not a search engine should be # used. If set to NO the values of all tags below this one will be ignored. SEARCHENGINE = NO pfstools-2.2.0/cmake_config.h.in0000664000701400070140000000235014105165602015241 0ustar rkm38rkm38/* config.h.in. Generated from configure.ac by autoheader. */ /* Define to the version of this package. */ #define PACKAGE_VERSION "${pfstools_VERSION_MAJOR}.${pfstools_VERSION_MINOR}" #define PACKAGE_STRING "pfstools ${pfstools_VERSION_MAJOR}.${pfstools_VERSION_MINOR}" /* Directory where data files are located. */ #define PKG_DATADIR "${PKG_DATADIR}" /* Define to 1 if you have the ANSI C header files. */ //#undef STDC_HEADERS /* Version number of package */ //#undef VERSION /* Windows compilation. */ //#undef WIN32 #if ${CYGWIN} #define CYGWIN #endif #if ${HAVE_OpenMP} #define HAVE_OpenMP #endif #if ${HAVE_FFTW3F} #define HAVE_FFTW3F #endif #if ${HAVE_FFTW3} #define HAVE_FFTW3 #endif /* Output stream for debug messages. */ #ifdef DEBUG #define DEBUG_STR std::cerr #else #define DEBUG_STR if(1) {;} else std::cerr #endif /* Output stream for verbose messages */ #define VERBOSE_STR if(verbose) std::cerr << PROG_NAME << ": " #if defined(_WIN32) || defined(_WIN64) #define strcasecmp _stricmp #define strncasecmp _strnicmp #endif #ifdef BRANCH_PREDICTION #define likely(x) __builtin_expect((x),1) #define unlikely(x) __builtin_expect((x),0) #else #define likely(x) (x) #define unlikely(x) (x) #endif pfstools-2.2.0/README.MinGW0000664000701400070140000000147214105165613013724 0ustar rkm38rkm38It is possible to compile pfstools with MinGW. This is in particular useful for compiling native version of pfsview on Windows 10. The compilation was tested on 6/10/2019 with MinGW 7.3.0 64-bit, installed as a part of Qt5 installation. To compile: 1) Install MinGW 7.3.0 64-bit from Qt5 maintenance tool (and relevant Qt5 libraries) 2) Start Qt5 MinGW shell (type MinGW in the Windows search box) 3) Go to the pfstools directory mkdir build_mingw cd build_mingw cmake -DWITH_OpenEXR=0 -G "MinGW Makefiles" ../ "WITH_OpenEXR=0" is needed to disable checking for ZLIB. 4) Compile everything mingw32-make If no "sed" command is installed, the installation may fail. In that case, add a line in src/fileformat/CMakeLists.txt: set( SHELL_CMDS ) and comment out: set( SHELL_CMDS pfsin pfsout pfsindcraw ) pfstools-2.2.0/CMakeLists.txt0000775000701400070140000002166214105165613014632 0ustar rkm38rkm38cmake_minimum_required (VERSION 2.8.8) project(pfstools) set(CPACK_GENERATOR TGZ) set(CPACK_SOURCE_GENERATOR TGZ) # CPack version numbers for release tarball name. set(CPACK_PACKAGE_VERSION_MAJOR 2) set(CPACK_PACKAGE_VERSION_MINOR 1) set(CPACK_PACKAGE_VERSION_PATCH 0) set(CPACK_SOURCE_IGNORE_FILES "~$" "\\\\.cvsignore$" "^${PROJECT_SOURCE_DIR}.*/CVS/" "^${PROJECT_SOURCE_DIR}/debian/" "^${PROJECT_SOURCE_DIR}/old/" ) include( CPack ) #cmake_policy(SET CMP0074 NEW) # This gets rid of some warning messages in MSVC add_definitions(-D_CRT_SECURE_NO_WARNINGS) #SET(ZLIB_DIR "C:/Users/manti/Documents/projects/vs2019_libs") #SET(CMAKE_INCLUDE_PATH ${ZLIB_DIR}/include ${CMAKE_INCLUDE_PATH}) #SET(CMAKE_LIBRARY_PATH ${ZLIB_DIR}/ ${ZLIB_DIR}/lib ${CMAKE_LIBRARY_PATH}) #set(CMAKE_PREFIX_PATH "C:\\Qt\\5.15.2\\msvc2019_64") if( "${CYGWIN}" STREQUAL "" ) set(CYGWIN 0) endif( "${CYGWIN}" STREQUAL "" ) set (pfstools_VERSION_MAJOR ${CPACK_PACKAGE_VERSION_MAJOR}) set (pfstools_VERSION_MINOR ${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}) set (pfslib_version 1.2) set(CMAKE_LEGACY_CYGWIN_WIN32 0) set (MAN_DIR "${CMAKE_INSTALL_PREFIX}/share/man/man1" CACHE PATH "The directory where the man pages are") include( CheckCXXSourceCompiles ) # ======== Check for OpenMP ======= find_package(OpenMP) if( OPENMP_FOUND ) set( HAVE_OpenMP 1 ) else( OPENMP_FOUND ) set( HAVE_OpenMP 0 ) endif( OPENMP_FOUND ) # ======== Has branch prediction ======= check_cxx_source_compiles( "int main() { int x = 0; if( __builtin_expect((x),0) ) x = 1; return 0; }" HAS_BRANCH_PEDICTION ) if( HAS_BRANCH_PREDICTION ) set( BRANCH_PREDICTION 1 ) else( HAS_BRANCH_PREDICTION ) set( BRANCH_PREDICTION 0 ) endif( HAS_BRANCH_PREDICTION ) # ======== Find bash ======= # TODO: What if the check fails find_program (BASH_EXECUTABLE bash) message( "Using bash: ${BASH_EXECUTABLE}" ) # ======== Find perl ======= find_program (PERL_EXECUTABLE perl) if( PERL_EXECUTABLE ) message( "Using perl: ${PERL_EXECUTABLE}" ) else( PERL_EXECUTABLE ) message( "Perl interpreter not found. Reading multiple exposures (pfsinhdrgen, pfsinme) will not work" ) endif( PERL_EXECUTABLE ) # ======== Build static or dynamic libraries ======= OPTION (BUILD_SHARED_LIBS "Build pfs as a shared library" OFF) # TODO: Shared libs do not work with VS. Add declspec as explained at: http://www.cmake.org/Wiki/BuildingWinDLL # Set the LIB_TYPE variable to STATIC SET (LIB_TYPE STATIC) IF (BUILD_SHARED_LIBS) SET (LIB_TYPE SHARED) ENDIF (BUILD_SHARED_LIBS) # ======== Check for getopt, which is missing on Windows ======== check_cxx_source_compiles( "#include \"getopt.h\" \n int main() { return 0; }" HAS_GETOPT ) if( NOT HAS_GETOPT ) MESSAGE( STATUS "getopt not found, a replacement needs to be compiled. " ) set( GETOPT_INCLUDE "${PROJECT_SOURCE_DIR}/src/getopt" ) set( GETOPT_OBJECT "$" ) else( NOT HAS_GETOPT ) MESSAGE( STATUS "getopt found. " ) set( GETOPT_INCLUDE ".") # work around, cannot pass empty string set( GETOPT_OBJECT ) endif( NOT HAS_GETOPT ) set( CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake ) # ======== OpenEXR =========== OPTION(WITH_OpenEXR "Compile with OpenEXR library" ON) if( WITH_OpenEXR ) find_package (OpenEXR) if( NOT OPENEXR_FOUND ) MESSAGE( STATUS "OpenEXR not found. The following command will not be compiled: pfsinexr pfsoutexr. " ) endif( NOT OPENEXR_FOUND ) endif( WITH_OpenEXR ) # ======== Image Magick =========== OPTION(WITH_ImageMagick "Use ImageMagick library" ON) if( WITH_ImageMagick ) find_package(ImageMagick COMPONENTS Magick++ MagickCore) if( NOT ImageMagick_FOUND ) MESSAGE( STATUS "ImageMagick not found. The following commands will not be compiled: pfsinimgmagick pfsoutimgmagick pfsouthdrhtml. " ) endif( NOT ImageMagick_FOUND ) else( WITH_ImageMagick ) set( ImageMagick_FOUND OFF ) endif( WITH_ImageMagick ) message( "ImageMagick CFLAGS: " ${ImageMagick_INCLUDE_DIRS} ) # ======== NetPBM =========== #include( ${PROJECT_SRC_DIR}/cmake/FindNETPBM.cmake ) OPTION(WITH_NetPBM "Use NetPBM library" ON) if( WITH_NetPBM ) find_package(NETPBM) if( NOT NETPBM_FOUND ) MESSAGE( STATUS "NetPBM not found. The following commands will not be compiled: pfsinppm pfsoutppm. " ) endif( NOT NETPBM_FOUND ) else( WITH_NetPBM ) set( NETPBM_FOUND OFF ) endif( WITH_NetPBM ) # ======== TIFF =========== #include( ${PROJECT_SRC_DIR}/cmake/FindTIDD.cmake ) OPTION(WITH_TIFF "Use TIFF library" ON) if( WITH_TIFF ) find_package(TIFF) if( NOT TIFF_FOUND ) MESSAGE( STATUS "TIFF not found. The following commands will not be compiled: pfsintiff pfsouttiff. " ) endif( NOT TIFF_FOUND ) else( WITH_TIFF ) set( NETPBM_FOUND OFF ) endif( WITH_TIFF ) # ======== QT =========== OPTION(WITH_QT "Use QT library" ON) if( WITH_QT ) find_package(Qt5 COMPONENTS Widgets) if( NOT Qt5Widgets_FOUND) MESSAGE( STATUS "Qt5 library not found. The following commands will not be compiled: pfsview. " ) else( NOT Qt5Widgets_FOUND ) MESSAGE( STATUS "Qt5 found" ) endif( NOT Qt5Widgets_FOUND ) else( WITH_QT ) set( QT_FOUND OFF ) endif( WITH_QT ) # ======== Matlab =========== OPTION(WITH_MATLAB "Compile Matlab MEX files" ON) SET (MATLAB_DEST_DIR "${CMAKE_INSTALL_PREFIX}/share/pfstools/pfstools_matlab" CACHE PATH "Copy matlab functions to this directory") SET (MEXGCC "" CACHE PATH "gcc compiler to use with MEX, for example MEXGCC=/usr/bin/gcc-4.7") if( WITH_MATLAB ) find_package(MATLAB) if( MATLAB_FOUND ) message(STATUS "matlab found. mex command: ${MATLAB_MEX}") elseif( MATLAB_FOUND ) message( STATUS "Matlab or mex compiler not found. Matlab integration will not be compiled." ) endif( MATLAB_FOUND ) endif( WITH_MATLAB ) #include( FindPkgConfig.cmake ) # ======== OpenGL =========== set(OpenGL_GL_PREFERENCE GLVND) OPTION(WITH_pfsglview "Compile pfsglview, requires OpenGL and GLUT" ON) if( WITH_pfsglview ) find_path(OPENGL_INCLUDE_DIR GL/gl.h ) find_package(OpenGL ) find_package(GLUT ) if( NOT OPENGL_FOUND ) MESSAGE( STATUS "OpenGL not found. The following command will not be compiled: pfsglview. " ) endif( NOT OPENGL_FOUND ) if( NOT GLUT_FOUND ) MESSAGE( STATUS "GLUT not found. The following command will not be compiled: pfsglview. " ) endif( NOT GLUT_FOUND ) endif( WITH_pfsglview ) # ========= FFTW =========== OPTION(WITH_FFTW "Use fftw to speed up some operations" ON) if( WITH_FFTW ) find_package (FFTW) if( NOT FFTW_FOUND ) MESSAGE( STATUS "FFTW library not found. Acceleration for pfstmo_durand02 disabled. " ) set( HAVE_FFTW3F 0 ) set( HAVE_FFTW3 0 ) else( NOT FFTW_FOUND ) set( HAVE_FFTW3F 1 ) set( HAVE_FFTW3 1 ) endif( NOT FFTW_FOUND ) else( WITH_FFTW ) set( HAVE_FFTW3F 0 ) set( HAVE_FFTW3 0 ) endif( WITH_FFTW ) # ========= GSL =========== OPTION(WITH_GSL "Use Gnu Scientific Libarary required for some TMOs" ON) if( WITH_GSL ) find_package (GSL) if( NOT GSL_FOUND ) MESSAGE( STATUS "GSL library not found. pfstmo_mantiuk08 will not be compiled." ) endif( NOT GSL_FOUND ) endif( WITH_GSL ) # ======== HDRVC =========== # This is an experimental implementation of HDR-MPEG codec, not included in the official release. OPTION(WITH_HDRVC "Compile HDRVC (internal release only)" OFF) # ======== MKOCTFILE/OCTAVE =========== OPTION(WITH_Octave "Compile Octave files" ON) SET (OCTAVE_DEST_DIR "" CACHE PATH "Install octave .m and .oct files in this directory") if( OCTAVE_DEST_DIR ) set( OCTAVE_OCT_DIR ${OCTAVE_DEST_DIR} ) set( OCTAVE_M_DIR ${OCTAVE_DEST_DIR} ) else( OCTAVE_DEST_DIR ) execute_process(COMMAND octave-config --oct-site-dir OUTPUT_VARIABLE OCTAVE_OCT_DIR OUTPUT_STRIP_TRAILING_WHITESPACE) set( OCTAVE_OCT_DIR "${OCTAVE_OCT_DIR}/pfstools" ) execute_process(COMMAND octave-config --m-site-dir OUTPUT_VARIABLE OCTAVE_M_DIR OUTPUT_STRIP_TRAILING_WHITESPACE) set( OCTAVE_M_DIR "${OCTAVE_M_DIR}/pfstools" ) endif( OCTAVE_DEST_DIR ) if( WITH_Octave ) find_program(MKOCTFILE mkoctfile) if(NOT MKOCTFILE) message(STATUS "Failed to find mkoctfile. Maybe liboctave-dev is not installed.") endif() if(MKOCTFILE) message(STATUS "mkoctfile found.") endif() endif( WITH_Octave ) # ======== OpenCV =========== OPTION(WITH_OpenCV "Use OpenCV library requires for pfsalign" ON) if( WITH_OpenCV ) find_package( OpenCV ) if( NOT OpenCV_FOUND ) message( "OpenCV library not found. 'pfsalign' will not be compiled" ) else( NOT OpenCV_FOUND ) message(STATUS "OpenCV library found.") endif( NOT OpenCV_FOUND ) endif( WITH_OpenCV ) # ======== libexif ========== find_package(EXIF) if( NOT EXIF_FOUND ) message( "EXIF library (libexif) not found. 'pfsalign' will not be compiled" ) else( NOT EXIF_FOUND ) message(STATUS "libexif library found.") endif( NOT EXIF_FOUND ) # ======== Config and sub dirs =========== set(PKG_DATADIR "${CMAKE_INSTALL_PREFIX}/share/pfstools") configure_file ( "${PROJECT_SOURCE_DIR}/cmake_config.h.in" "${PROJECT_BINARY_DIR}/config.h" ) add_subdirectory (src) pfstools-2.2.0/src/0002775000701400070140000000000014105165615012653 5ustar rkm38rkm38pfstools-2.2.0/src/filter/0002775000701400070140000000000014105165613014136 5ustar rkm38rkm38pfstools-2.2.0/src/filter/pfstag.10000664000701400070140000000225414105165613015505 0ustar rkm38rkm38.TH "pfstag" 1 .SH NAME pfstag \- Set or remove tags to/from pfs stream .SH SYNOPSIS .B pfstag [--set [channel:]name=value] [--remove [channel:]name] .SH DESCRIPTION Use this command to set or remove tags from the pfs-stream. Tags are used to add additional information to pfs frames and they are in the format: 'name=value'. To learn more about tags, read 'Specification of the PFS File Format'. Tags are set/removed to/from all pfs frames in the stream. Note that currently only OpenEXR file format supports tags. .SH OPTIONS .TP --set [channel:]name=value], -s [channel:]name=value], --add [channel:]name=value] Change existing or add a new tag of the given name. If no channel is given, tags are added to the frame. .TP --remove [channel:]name], -r [channel:]name] Remove tag of the given name. Ignore if the tag does not exist. If no channel is given, tags are removed from the frame. .SH EXAMPLES .TP pfsin memorial.hdr | pfstag --add "EXTRA_INFO=foo" | pfsout memorial_ei.exr Add tag "EXTRA_INFO=foo" to the memorial image and save it as memorial_ei.exr. .SH "SEE ALSO" .BR pfsin (1) .BR pfsout (1) .SH BUGS Please report bugs and comments to Rafal Mantiuk . pfstools-2.2.0/src/filter/pfsflip.10000664000701400070140000000155414105165613015666 0ustar rkm38rkm38.TH "pfsflip" 1 .SH NAME pfsflip \- Flip images horizontally and/or vertically. .SH SYNOPSIS .B pfsflip [--h] [--v] [--help] .SH DESCRIPTION Use this command to flip images from a stream of pfs. The --h option results in a horizontal exchange, and the --v option results in a vertical exchange. Both options may be applied, which is equivalent to rotation of the image 180 degrees. .SH OPTIONS .TP --h, -h Flip the image horizontally. .TP --v, -v Flip the image vertically. .SH EXAMPLES .TP pfsin memorial.hdr | pfsflip -h -v | pfsout memorial_gc.hdr Flip memorial image horizontally and vertically (rotate the image 180 degrees) and save to memorial_gc.hdr. .SH "NOTES" Notice that either --h or --v must be specified. .SH "SEE ALSO" .BR pfsin(1) .BR pfsout(1) .BR pfsrotate(1) .SH BUGS Please report bugs and comments to Alexander Efremov . pfstools-2.2.0/src/filter/pfsabsolute.10000664000701400070140000000360614105165613016552 0ustar rkm38rkm38.TH "pfsabsolute" 1 .SH NAME pfsabsolute \- Convert luminance in images to absolute measure .SH SYNOPSIS .B pfsabsolute [] [--verbose] [--help] .SH DESCRIPTION \fBpfsabsolute\fR applies all necessary operations to convert an image from relative luminance (tag LUMINANCE is RELATIVE) or display-dependent luma (tag LUMINANCE is DISPLAY) to absolute luminance values. When the luminance in an image is absolute (tag LUMINANCE set to ABSOLUTE), the Y channel represents physical luminance in cd/m^2. Absolute luminance levels are useful for some tone mapping algorithms and also for image or video compression. The argument \fB\fR denotes the level of luminance that relative luminance \fB\fR should be rescaled to. The luminance is in fact multiplied by the ratio /. \fB\fR is normally relative luminance checked with pfsview in the spot where the absolute luminance \fB\fR is measured, known or guessed. If \fB\fR is omitted, the value 1 is assumed, so \fB\fR is just a scaling factor. In case if display-depended luma (tag LUMINANCE is DISPLAY), the inverse gamma correction is applied (assuming sRGB color space). In such case \fB\fR is usually the maximum luminance of a display (e.g. 80 cd/m^2). This command always sets LUMINANCE tag to ABSOLUTE. .SH EXAMPLES .TP pfsin memorial.hdr | pfsabsolute 20 0.04 | pfsview Multiply luminance in memorial image, so that relative luminance 0.04 becomes 20 cd/m^2. Normally, the value 0.04 is check using pfsview in the spot, where the absolute luminance (which equals in this case 20 cd/m^2) is known. .TP pfsin lena.png | pfsabsolute 80 | pfsview Convert lena image from sRGB color space to absolute XYZ, assuming maximum luminance of the monitor 80 cd/m^2. .SH "SEE ALSO" .BR pfsgamma (1) .BR pfsinppm (1) .SH BUGS Please report bugs and comments to Rafal Mantiuk . pfstools-2.2.0/src/filter/pfsgamma.10000664000701400070140000000377314105165613016023 0ustar rkm38rkm38.TH "pfsgamma" 1 .SH NAME pfsgamma \- Apply gamma correction to color or gray-scale images .SH SYNOPSIS .B pfsgamma [--\fBgamma\fR | --\fBinverse-gamma\fR ] [--\fBmul\fR ] .SH DESCRIPTION Use this command to gamma correct a stream of pfs images. Gamma correction is equivalent to raising value of R, G and B (or Y for gray-scale images) channels to 1/gamma power. More information on gamma correction can be found at: .PP http://www.poynton.com/notes/colour_and_gamma/GammaFAQ.html .PP This command can work on color images, in which case XYZ channels are transformed to RGB space, then they are gamma corrected before they are converted back to XYZ. For gray-scale images (only Y channel and XZ missing) gamma correction is applied only to Y channel. .PP Data can be multiplied by an optional multiplier \fBbefore\fR gamma correction. .PP Note: gamma correction will set the 'LUMINANCE' tag to 'DISPLAY' thus cancelling any sRGB correction when saving to LDR files (like PPM, TIFF). This will also cause a warning message when saving to HDR files, because the intensities after gamma correction are not linearly related to luminance any more. .SH OPTIONS .TP --\fBgamma\fR , -\fBg\fR Perform gamma correction (input^(1/gamma)). This can be used to convert images from relative luminance or radiance to pixel values. Default value: 1.0 .TP --\fBinverse-gamma\fR , -\fBi\fR Perform inverse gamma correction (input^(gamma)). This can be used to convert images from pixel values to relative radiance or luminance. .TP --\fBmul\fR , -\fBm\fR Multiply data by this value before gamma correction. Default value: 1 .SH EXAMPLES .TP pfsin memorial.hdr | pfsgamma -g 1.8 -m 10 | pfsout memorial_gc.ppm .IP Multiply memorial image by 10, gamma correct it and save to memorial_gc.ppm. .SH "SEE ALSO" .BR pfsin (1) .BR pfsout (1) .BR pfsdisplayfunction (1) .SH BUGS Please report bugs and comments to the pfstools discussion group (http://groups.google.com/group/pfstools). pfstools-2.2.0/src/filter/display_function.cpp0000664000701400070140000001667114105165613020225 0ustar rkm38rkm38/** * @brief Display Adaptive TMO * * From: * Rafal Mantiuk, Scott Daly, Louis Kerofsky. * Display Adaptive Tone Mapping. * To appear in: ACM Transactions on Graphics (Proc. of SIGGRAPH'08) 27 (3) * http://www.mpi-inf.mpg.de/resources/hdr/datmo/ * * This file is a part of PFSTMO package. * ---------------------------------------------------------------------- * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Rafal Mantiuk, * * $Id: display_function.cpp,v 1.3 2014/04/05 22:04:12 rafm Exp $ */ #include #include #include #include #include #include "config.h" #include "display_function.h" static void removeCommandLineArg( int &argc, char* argv[], int firstArgToRemove, int numArgsToRemove ); // ========== GGBA Display Function ============== DisplayFunctionGGBA::DisplayFunctionGGBA( float gamma, float L_max, float L_black, float E_amb, float screen_refl ) { init( gamma, L_max, L_black, E_amb, screen_refl ); } void DisplayFunctionGGBA::init( float gamma, float L_max, float L_black, float E_amb, float screen_refl ) { const float PI = 3.1415926535; this->gamma = gamma; this->L_max = L_max; this->L_black = L_black; this->E_amb = E_amb; this->screen_refl = screen_refl; this->L_offset = L_black + screen_refl/PI*E_amb; } DisplayFunctionGGBA::DisplayFunctionGGBA( const char *predefined ) { if( !strcasecmp( predefined, "lcd_office" ) ) { init( 2.2f, 100, 0.8, 400, 0.01 ); } else if( !strcasecmp( predefined, "lcd" ) ) { init( 2.2f, 200, 0.8, 60, 0.01 ); } else if( !strcasecmp( predefined, "lcd_bright" ) ) { init( 2.6f, 500, 0.5, 10, 0.01 ); } else if( !strcasecmp( predefined, "crt" ) ) { init( 2.2f, 80, 1, 60, 0.02 ); } else { throw pfs::Exception( "Unknown display type. Recognized types: lcd_office, lcd, lcd_bright, crt." ); } } void DisplayFunctionGGBA::print( FILE *fh ) { fprintf( fh, "Display function: gamma-gain-black-ambient\n" ); fprintf( fh, " gamma = %g\t L_max = %g\t L_black = %g\n", (double)gamma, (double)L_max, (double)L_black ); fprintf( fh, " E_amb = %g\t k = %g\n", (double)E_amb, (double)screen_refl ); } float DisplayFunctionGGBA::inv_display( float L ) { if( L < L_offset ) L = L_offset; if( L > (L_offset+L_max) ) L = L_offset + L_max; return powf( (L - L_offset)/(L_max-L_black), 1/gamma ); } float DisplayFunctionGGBA::display( float pix ) { if( pix < 0 ) pix = 0; if( pix > 1 ) pix = 1; return pow( pix, gamma ) * (L_max-L_black) + L_offset; } // ========== LUT Display Function ============== static const size_t max_lut_size = 4096; DisplayFunctionLUT::DisplayFunctionLUT( const char *file_name ) : pix_lut( NULL ), L_lut( NULL ) { FILE *fh = fopen( file_name, "r" ); if( fh == NULL ) throw pfs::Exception( "Cannot open lookup-table file" ); L_lut = new float[max_lut_size]; pix_lut = new float[max_lut_size]; const size_t max_line = 100; char buf[max_line]; int i = 0; while( fgets( buf, max_line, fh ) != NULL ) { float p_buf, L_buf; if( sscanf( buf, "%f%*[ ,;]%f\n", &p_buf, &L_buf ) != 2 ) continue; if( p_buf < 0 || p_buf > 1 ) throw pfs::Exception( "Improper LUT: pixel values must be from 0 to 1" ); if( L_buf <= 0 ) throw pfs::Exception( "Improper LUT: luminance must be greater than 0" ); L_lut[i] = log10( L_buf ); pix_lut[i] = p_buf; i++; if( i >= max_lut_size ) throw pfs::Exception( "LUT too large (more than 4096 entries)" ); } lut_size = i; if( pix_lut[0] != 0 || pix_lut[lut_size-1]!= 1 ) throw pfs::Exception( "The first and last LUT entries for pixel value should be 0 and 1" ); } DisplayFunctionLUT::~DisplayFunctionLUT() { delete []pix_lut; delete []L_lut; } inline float bin_search_interp( float x, const float *lut_x, const float *lut_y, const int lutSize ) { if( x <= lut_x[0] ) return lut_y[0]; if( x >= lut_x[lutSize-1] ) return lut_y[lutSize-1]; size_t l = 0, r = lutSize; while( true ) { size_t m = (l+r)/2; if( m == l ) break; if( x < lut_x[m] ) r = m; else l = m; } return lut_y[l] + (lut_y[l+1]-lut_y[l])*(x-lut_x[l])/(lut_x[l+1]-lut_x[l]); } float DisplayFunctionLUT::inv_display( float L ) { return bin_search_interp( log10( L ), L_lut, pix_lut, lut_size ); } float DisplayFunctionLUT::display( float pix ) { return pow( 10, bin_search_interp( pix, pix_lut, L_lut, lut_size ) ); } void DisplayFunctionLUT::print( FILE *fh ) { fprintf( fh, "Display function: lookup-table\n" ); fprintf( fh, " L_min = %g \tL_max = %g\n", (double)pow( 10, L_lut[0] ), (double)pow( 10, L_lut[lut_size-1] ) ); } // ========== Command line parsing ============== DisplayFunction *createDisplayFunctionFromArgs( int &argc, char* argv[] ) { DisplayFunction *df = 0; for( int i=1 ; i= argc ) throw pfs::Exception( "missing display function specification" ); float gamma = 2.2f, L_max = 100.f, L_black = 1.f, k = 0.01, E_amb = 50; bool GGBA_model = true; char *token; token = strtok( argv[i+1], ":" ); while( token != NULL ) { if( !strncmp( token, "pd=", 3 ) ) { df = new DisplayFunctionGGBA( token+3 ); GGBA_model = false; break; } else if( !strncmp( token, "lut=", 4 ) ) { df = new DisplayFunctionLUT( token+4 ); GGBA_model = false; break; } else if( !strncmp( token, "g=", 2 ) ) { gamma = strtod( token+2, NULL ); } else if( !strncmp( token, "l=", 2 ) ) { L_max = strtod( token+2, NULL ); } else if( !strncmp( token, "b=", 2 ) ) { L_black = strtod( token+2, NULL ); } else if( !strncmp( token, "k=", 2 ) ) { k = strtod( token+2, NULL ); } else if( !strncmp( token, "a=", 2 ) ) { E_amb = strtod( token+2, NULL ); } else { throw pfs::Exception( "Bad display type specification" ); } token = strtok( NULL, ":" ); } if( GGBA_model ) df = new DisplayFunctionGGBA( gamma, L_max, L_black, E_amb, k ); removeCommandLineArg( argc, argv, i, 2 ); break; } } return df; } static void removeCommandLineArg( int &argc, char* argv[], int firstArgToRemove, int numArgsToRemove ) { assert( firstArgToRemove+numArgsToRemove <= argc ); if( argc-firstArgToRemove-numArgsToRemove > 0 ) { for( int i = firstArgToRemove; i < argc-numArgsToRemove; i++ ) argv[i] = argv[i+numArgsToRemove]; } argc -= numArgsToRemove; } pfstools-2.2.0/src/filter/pfscat.10000664000701400070140000000274214105165613015503 0ustar rkm38rkm38.TH "pfscat" 1 .SH NAME pfscat \- Concatenate frames in PFS stream .SH SYNOPSIS .B pfscat [--horizontal] [--vertical] [-j ] [-R ] [-G ] [-B ] [-Y ] [--help] image1.pfs image2.pfs ... .SH DESCRIPTION Read number of frames as input, stitch them either horizontally or vertically, and produce single frame as output. If frames are not all the same size they are justified with each other - by default they are centered, but can also be flushed with a specified edge. .SH OPTIONS .TP --horizontal, -H Stitch frames horizontally. .TP --vertical, -V Stitch frames vertically. .TP -j Specify justification of input frames. Predefined types are: min, max, center. .TP -R , -G , -B Color of an extra space in a resulting image. Default color is black, and if some of the components is not specified, its value is set to 0.0. .TP -Y Color of an extra space in luminance mode. .TP --help, -h Print a list of commandline options. .SH EXAMPLES .TP pfsinmulti anim1_%04d.hdr anim2_%04d.hdr -- pfscat @1 @2 --vertical Stitch two hdr animations in vertical alignment. 'pfscat' is taken as an argument by 'pfsinmulti', therefore it must be preceded with '--'. Number of @1 @2 ... @n arguments must be the same as number of animations to combine. .SH NOTES Note that either --horizontal or --vertical option must be specified. .SH SEE ALSO .BR pfsin (1) .BR pfsout (1) .SH BUGS Please report bugs and comments to Dorota Zdrojewska . pfstools-2.2.0/src/filter/CMakeLists.txt0000664000701400070140000000157414105165613016703 0ustar rkm38rkm38include_directories ("${PROJECT_BINARY_DIR}/" "${PROJECT_SOURCE_DIR}/src/pfs") if( NOT HAS_GETOPT ) include_directories ("${GETOPT_INCLUDE}") endif( NOT HAS_GETOPT ) link_directories("${PROJECT_SOURCE_DIR}/src/pfs") set(PFS_FILT pfsgamma pfsclamp pfstag pfssize pfsextractchannels pfspanoramic pfsrotate pfsflip pfscut pfspad pfscat pfsabsolute pfsretime pfscolortransform) foreach(TRG ${PFS_FILT}) add_executable(${TRG} ${TRG}.cpp "${GETOPT_OBJECT}") target_link_libraries(${TRG} pfs) install (TARGETS ${TRG} DESTINATION bin) install (FILES ${TRG}.1 DESTINATION ${MAN_DIR}) endforeach(TRG) add_executable(pfsdisplayfunction pfsdisplayfunction.cpp display_function.cpp display_function.h "${GETOPT_OBJECT}") target_link_libraries(pfsdisplayfunction pfs) install (TARGETS pfsdisplayfunction DESTINATION bin) install (FILES pfsdisplayfunction.1 DESTINATION ${MAN_DIR}) pfstools-2.2.0/src/filter/pfsclamp.cpp0000664000701400070140000001350714105165613016453 0ustar rkm38rkm38/** * @brief Clamp values of X, Y, Z channels in PFS stream * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Rafal Mantiuk and Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Rafal Mantiuk, * * $Id: pfsclamp.cpp,v 1.3 2014/04/05 22:04:12 rafm Exp $ */ #include #include #include #include #include #include #include #include #define PROG_NAME "pfsclamp" class QuietException { }; void percentile(const pfs::Array2D *I, float& percMin, float& percMax ) { int size = I->getRows() * I->getCols(); std::vector vI; for( int i=0 ; igetRows()*array->getCols(); if( opt_percentile ) percentile(array,min,max); float minval=min; float maxval=max; if( opt_zeromode ) minval = maxval = 0.0f; for( int index = 0; index < imgSize ; index++ ) { float &v = (*array)(index); if( v < min ) v = minval; else if( v > max ) v = maxval; if( !std::isfinite(v) ) v = maxval; } } void printHelp() { fprintf( stderr, PROG_NAME " [--min ] [--max ] [--verbose] [--help]\n" "See man page for more information.\n" ); } void clampFrames( int argc, char* argv[] ) { pfs::DOMIO pfsio; float clampMin = 0.0001; // default: 10^-4 float clampMax = 100000000; // default: 10^8 bool verbose = false; bool opt_percentile = false; bool opt_zeromode = false; bool opt_rgbmode = false; static struct option cmdLineOptions[] = { { "help", no_argument, NULL, 'h' }, { "verbose", no_argument, NULL, 'v' }, { "percentile", no_argument, NULL, 'p' }, { "zero", no_argument, NULL, 'z' }, { "rgb", no_argument, NULL, 'r' }, { "min", required_argument, NULL, 'n' }, { "max", required_argument, NULL, 'x' }, { NULL, 0, NULL, 0 } }; int optionIndex = 0; while( 1 ) { int c = getopt_long (argc, argv, "hvpz", cmdLineOptions, &optionIndex); if( c == -1 ) break; switch( c ) { case 'h': printHelp(); throw QuietException(); case 'v': verbose = true; break; case 'p': opt_percentile = true; break; case 'z': opt_zeromode = true; break; case 'r': opt_rgbmode = true; break; case 'n': clampMin = (float)strtod( optarg, NULL ); break; case 'x': clampMax = (float)strtod( optarg, NULL ); break; case '?': throw QuietException(); case ':': throw QuietException(); } } // check if clamping parameters make sense if( opt_percentile ) { clampMin = (clampMin>1e-3) ? clampMin : 1e-3; clampMax = (clampMax<1) ? clampMax : 0.999f; if( clampMin >= clampMax ) throw pfs::Exception("incorrect clamping range for percentile mode"); } else { clampMin = (clampMin>1e-4) ? clampMin : 1e-4; clampMax = (clampMax<1e8) ? clampMax : 1e8; if( clampMin >= clampMax ) throw pfs::Exception("incorrect clamping range"); } if( verbose ) { if( opt_rgbmode ) fprintf(stderr, "Clamping in RGB color space.\n"); if( opt_zeromode ) fprintf(stderr, "Values out of clamp range will be set to zero.\n"); if( opt_percentile ) fprintf( stderr, "Clamping channels to [%g, %g] percentile.\n", clampMin, clampMax ); else fprintf( stderr, "Clamping channels to [%g, %g] range.\n", clampMin, clampMax ); } while( true ) { pfs::Frame *frame = pfsio.readFrame( stdin ); if( frame == NULL ) break; // No more frames pfs::Channel *X, *Y, *Z; frame->getXYZChannels( X, Y, Z ); if( X != NULL ) { // Color, XYZ if( opt_rgbmode ) pfs::transformColorSpace( pfs::CS_XYZ, X, Y, Z, pfs::CS_RGB, X, Y, Z ); clamp( X, clampMin, clampMax, opt_percentile, opt_zeromode ); clamp( Y, clampMin, clampMax, opt_percentile, opt_zeromode ); clamp( Z, clampMin, clampMax, opt_percentile, opt_zeromode ); if( opt_rgbmode ) pfs::transformColorSpace( pfs::CS_RGB, X, Y, Z, pfs::CS_XYZ, X, Y, Z ); } else if( (Y = frame->getChannel( "Y" )) != NULL ) { clamp( Y, clampMin, clampMax, opt_percentile, opt_zeromode ); } else throw pfs::Exception( "Missing X, Y, Z channels in the PFS stream" ); pfsio.writeFrame( frame, stdout ); pfsio.freeFrame( frame ); } } int main( int argc, char* argv[] ) { try { clampFrames( argc, argv ); } catch( pfs::Exception ex ) { fprintf( stderr, PROG_NAME " error: %s\n", ex.getMessage() ); return EXIT_FAILURE; } catch( QuietException ex ) { return EXIT_FAILURE; } return EXIT_SUCCESS; } pfstools-2.2.0/src/filter/pfsflip.cpp0000664000701400070140000001004314105165613016301 0ustar rkm38rkm38/** * @brief Flip images in PFS stream * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Rafal Mantiuk and Grzegorz Krawczyk, * Alexander Efremov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Alexander Efremov, * * $Id: pfsflip.cpp,v 1.1 2005/06/15 13:36:54 rafm Exp $ */ #include #include #include #include #include #include #define PROG_NAME "pfsflip" class QuietException { }; void flipArray( const pfs::Array2D *in, pfs::Array2D *out, bool h, bool v ); void printHelp() { fprintf( stderr, PROG_NAME " [-h] [-v] [--help]\n" "See man page for more information.\n" ); } void flipFrames( int argc, char* argv[] ) { pfs::DOMIO pfsio; static struct option cmdLineOptions[] = { { "help", no_argument, NULL, '1' }, { "h", no_argument, NULL, 'h' }, { "v", no_argument, NULL, 'v' }, { NULL, 0, NULL, 0 } }; bool h = false; bool v = false; int optionIndex = 0; while( 1 ) { int c = getopt_long (argc, argv, "hv", cmdLineOptions, &optionIndex); if( c == -1 ) break; switch( c ) { case '1': printHelp(); throw QuietException(); case 'h': h = true; break; case 'v': v = true; break; case '?': throw QuietException(); case ':': throw QuietException(); } } if( (!h) & (!v) ) throw pfs::Exception( "Either --h or --v must be specified" ); bool firstFrame = true; pfs::Frame *resizedFrame = NULL; while( true ) { pfs::Frame *frame = pfsio.readFrame( stdin ); if( frame == NULL ) break; // No more frames pfs::Channel *X, *Y, *Z; frame->getXYZChannels( X, Y, Z ); pfs::Channel *dX, *dY, *dZ; if( firstFrame ) { int xSize = frame->getWidth(); int ySize = frame->getHeight(); resizedFrame = pfsio.createFrame( xSize, ySize ); firstFrame = false; } pfs::ChannelIterator *it = frame->getChannels(); while( it->hasNext() ) { pfs::Channel *originalCh = it->getNext(); pfs::Channel *newCh = resizedFrame->createChannel( originalCh->getName() ); flipArray( originalCh, newCh, h, v ); } pfs::copyTags( frame, resizedFrame ); pfsio.writeFrame( resizedFrame, stdout ); pfsio.freeFrame( frame ); } pfsio.freeFrame( resizedFrame ); } void flipArray(const pfs::Array2D *in, pfs::Array2D *out, bool h, bool v ) { int outRows = out->getRows(); int outCols = out->getCols(); if( h & v ) { for( int i=0; i] [--max ] [--percentile] [--zero] [--rgb] .SH DESCRIPTION Use this command to clamp values of luminance and color channels to be within the specified range. I.e. if a value in the channel is above the specified maximum or below specified minimum, set the value to either minimum or maximum. The command operates directly on XYZ channels. .SH OPTIONS .TP --min Lower bound for clamping. Default value: 0.0001 (10^-4) .TP --max Upper bound for clamping. Default value: 100000000 (10^8) .TP --percentile, -p Treat given min and max values as a percentile. .TP --zero, -z Set values out of clamping range to zero, instead of setting to specified maximum and minimum. .TP --rgb Perform clamping in RGB space. .SH EXAMPLES .TP pfsin memorial.hdr | pfsclamp | pfsout memorial_cl.hdr Remove possible out-of-range values, for examples zeros, from memorial image. .TP pfsin memorial.hdr | pfsclamp --max 0.95 -p | pfsout memorial_cl.hdr Remove 5% of the brightest pixels from the original image. .SH "SEE ALSO" .BR pfsin (1) .BR pfsout (1) .SH BUGS Please report bugs and comments to Rafal Mantiuk and Grzegorz Krawczyk . pfstools-2.2.0/src/filter/pfsgamma.cpp0000664000701400070140000001250114105165613016432 0ustar rkm38rkm38/** * @brief Apply gamma correction the the pfs stream * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Rafal Mantiuk and Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Rafal Mantiuk, * * $Id: pfsgamma.cpp,v 1.4 2013/01/29 22:15:01 rafm Exp $ */ #include #include #include #include #include #include #include #define PROG_NAME "pfsgamma" class QuietException { }; void applyGamma( pfs::Array2D *array, const float exponent, const float multiplier ); void printHelp() { fprintf( stderr, PROG_NAME " [--gamma | --inverse-gamma ] [--mul ] [--verbose] [--help]\n" "See man page for more information.\n" ); } void retimeFrames( int argc, char* argv[] ) { pfs::DOMIO pfsio; float gamma = 1.0f; bool opt_setgamma = false; float multiplier = 1; bool verbose = false; static struct option cmdLineOptions[] = { { "help", no_argument, NULL, 'h' }, { "verbose", no_argument, NULL, 'v' }, { "gamma", required_argument, NULL, 'g' }, { "inverse-gamma", required_argument, NULL, 'i' }, { "mul", required_argument, NULL, 'm' }, { NULL, 0, NULL, 0 } }; int optionIndex = 0; while( 1 ) { int c = getopt_long (argc, argv, "m:g:i:hv", cmdLineOptions, &optionIndex); if( c == -1 ) break; switch( c ) { case 'h': printHelp(); throw QuietException(); case 'v': verbose = true; break; case 'g': gamma = (float)strtod( optarg, NULL ); opt_setgamma = true; break; case 'i': gamma = 1/(float)strtod( optarg, NULL ); opt_setgamma = true; break; case 'm': multiplier = (float)strtod( optarg, NULL ); break; case '?': throw QuietException(); case ':': throw QuietException(); } } VERBOSE_STR << "multiplier: " << multiplier << " gamma: " << gamma << std::endl; if( gamma > 1.0f ) VERBOSE_STR << "luminance content will be changed to 'display'" << std::endl; bool first_frame = true; while( true ) { pfs::Frame *frame = pfsio.readFrame( stdin ); if( frame == NULL ) break; // No more frames if( first_frame ) { const char *lum_type = frame->getTags()->getString("LUMINANCE"); if( lum_type ) { if( !strcmp( lum_type, "DISPLAY" ) && gamma > 1.0f ) std::cerr << PROG_NAME " warning: applying gamma correction to a display referred image" << std::endl; if( !strcmp( lum_type, "RELATIVE" ) && gamma < 1.0f ) std::cerr << PROG_NAME " warning: applying inverse gamma correction to a linear luminance or radiance image" << std::endl; if( !strcmp( lum_type, "ABSOLUTE" ) && multiplier == 1 ) std::cerr << PROG_NAME " warning: an image should be normalized to 0-1 before applying gamma correction" << std::endl; } } pfs::Channel *X, *Y, *Z; frame->getXYZChannels( X, Y, Z ); if( X != NULL ) { // Color, XYZ pfs::transformColorSpace( pfs::CS_XYZ, X, Y, Z, pfs::CS_RGB, X, Y, Z ); // At this point (X,Y,Z) = (R,G,B) applyGamma( X, 1/gamma, multiplier ); applyGamma( Y, 1/gamma, multiplier ); applyGamma( Z, 1/gamma, multiplier ); pfs::transformColorSpace( pfs::CS_RGB, X, Y, Z, pfs::CS_XYZ, X, Y, Z ); // At this point (X,Y,Z) = (X,Y,Z) } else if( (Y = frame->getChannel( "Y" )) != NULL ) { // Luminance only applyGamma( Y, 1/gamma, multiplier ); } else throw pfs::Exception( "Missing X, Y, Z channels in the PFS stream" ); if( opt_setgamma && gamma > 1.0f ) frame->getTags()->setString("LUMINANCE", "DISPLAY"); else if( opt_setgamma && gamma < 1.0f ) frame->getTags()->setString("LUMINANCE", "RELATIVE"); first_frame = false; pfsio.writeFrame( frame, stdout ); pfsio.freeFrame( frame ); } } void applyGamma( pfs::Array2D *array, const float exponent, const float multiplier ) { int imgSize = array->getRows()*array->getCols(); for( int index = 0; index < imgSize ; index++ ) { float &v = (*array)(index); if( v < 0 ) v = 0; v = powf( v*multiplier, exponent ); } } int main( int argc, char* argv[] ) { try { retimeFrames( argc, argv ); } catch( pfs::Exception ex ) { fprintf( stderr, PROG_NAME " error: %s\n", ex.getMessage() ); return EXIT_FAILURE; } catch( QuietException ex ) { return EXIT_FAILURE; } return EXIT_SUCCESS; } pfstools-2.2.0/src/filter/pfsextractchannels.10000664000701400070140000000077414105165613020125 0ustar rkm38rkm38.TH "pfsextractchannels" 1 .SH NAME pfsextractchannels \- Extract selected channels from the stream .SH SYNOPSIS .B pfsextractchannels [ ...] .SH DESCRIPTION Removes all channels from the pfs stream that are not listed in arguments. .SH EXAMPLES .TP pfsin memorial.hdr | pfsextractchannels Y | pfsview View luminance channel of the memorial image. .SH "SEE ALSO" .BR pfsin (1) .BR pfsout (1) .SH BUGS Please report bugs and comments to Rafal Mantiuk . pfstools-2.2.0/src/filter/pfscat.cpp0000664000701400070140000002477514105165613016137 0ustar rkm38rkm38/** * @brief Concatenate frames in PFS stream * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Rafal Mantiuk and Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Dorota Zdrojewska, * */ #include #include #include #include #include #include #define PROG_NAME "pfscat" #define UNSP INT_MAX #define HORIZONTAL 0 #define VERTICAL 1 #define MIN 0 #define MAX 1 #define PREV_MIN 2 #define PREV_MAX 3 #define X 0 #define Y 1 #define Z 2 using namespace std; enum just {J_MIN, J_MAX, J_CENTER}; class QuietException { }; static struct option cmdLineOptions[] = { {"help", no_argument, NULL, 'h'}, {"horizontal", no_argument, NULL, 'H'}, {"vertical", no_argument, NULL, 'V'}, {"justify", required_argument, NULL, 'j'}, {"R", required_argument, NULL, 'R'}, {"G", required_argument, NULL, 'G'}, {"B", required_argument, NULL, 'B'}, {"Y", required_argument, NULL, 'Y'}, {NULL, 0, NULL, 0} }; void printHelp() { fprintf( stderr, PROG_NAME ": \n" "\t[--vertical]\n" "\t[--horizontal]\n" "\t[-j ]\n" "\t[-R ]\n" "\t[-G ]\n" "\t[-B ]\n" "\t[-Y ]\n" "\t[--help]\n" "See man page for more information.\n"); } void justification (enum just just, int inSize, int prevSize, int outSize, int *out) { switch (just) { case J_CENTER: if(inSizemax) ? max : val; return out; } void pfscat (int argc, char* argv[]) { pfs::DOMIO pfsio; static const char optString[] = "hHVj:R:G:B:Y:"; enum just just=J_CENTER; // type of justification int alignment=UNSP; //vertical or horizontal alignment of frames in output image float R=1e-4, G=1e-4, B=1e-4, luminance=1.0; // background color in RGB or luminance mode float XYZ[3]; bool optLuminanceMode=false, RGB=false, optLuminance=false; bool pipes = true; int optionIndex=0; while (1) { int c=getopt_long(argc, argv,optString, cmdLineOptions, &optionIndex); if(c==-1) break; switch(c) { case 'h': printHelp(); throw QuietException(); break; case 'H': if (alignment==VERTICAL) throw pfs::Exception("You cannot specify both horizontal and vertical alignment"); else alignment=HORIZONTAL; break; case 'V': if (alignment==HORIZONTAL) throw pfs::Exception("You cannot specify both horizontal and vertical alignment"); else alignment=VERTICAL; break; case 'j': if (!strcmp(optarg,"min")) just=J_MIN; else if (!strcmp(optarg,"max")) just=J_MAX; else if (!strcmp(optarg,"center")) just=J_CENTER; else throw pfs::Exception("Unknown type of justification. Accepted types: 'min', 'max' or 'center')"); break; case 'R': R=atof(optarg); if(R<=(1e-4)) R=1e-4; RGB=true; break; case 'G': G=atof(optarg); if(G<=(1e-4)) G=1e-4; RGB=true; break; case 'B': B=atof(optarg); if(B<=(1e-4)) B=1e-4; RGB=true; break; case 'Y': luminance=atof(optarg); if(luminance<=(1e-4)) luminance=1e-4; optLuminance=true; break; } } if (alignment==UNSP) throw pfs::Exception("You must specify an alignment - --horizontal or --vertical option"); pfs::FrameFileIterator it ( argc, argv, "rb", NULL, NULL, optString, cmdLineOptions); int pipe_no = 0; pfs::FrameFile * ff; ff = (pfs::FrameFile *) malloc ((pipe_no+1) * sizeof(pfs::FrameFile)); while (1) { ff[pipe_no] = it.getNextFrameFile(); if (ff[pipe_no].fh == NULL) break; // no more files pipe_no++; ff=(pfs::FrameFile *) realloc(ff, (pipe_no+1)*sizeof(pfs::FrameFile)); } if(pipe_no == 0) { // no named pipes pipe_no = 1; pipes = false; } //color transformation if(optLuminance && !RGB) { R=1; G=1; B=1; } XYZ[X]=luminance*(0.4124*R + 0.3576*G + 0.1805*B); XYZ[Y]=luminance*(0.2126*R + 0.7152*G + 0.0722*B); XYZ[Z]=luminance*(0.0193*R + 0.1192*G + 0.9505*B); for (int i=0; i<3; i++) XYZ[i]=clamp(XYZ[i], 1e-4, 100000000.0); bool finish = false; int frame_no=0; int tRow=0, bRow=0, lCol=0, rCol=0, prevtRow=0, prevbRow=0, prevlCol=0, prevrCol=0; int inWidth=0, inHeight=0, outWidth=0, outHeight=0, prevWidth=0, prevHeight=0; pfs::Frame *inFrame, *outFrame, *prevFrame; pfs::Channel *inX, *inY, *inZ, *outX, *outY, *outZ, *prevX, *prevY, *prevZ; while (1) { if(pipes) { inWidth=0; inHeight=0; outWidth=0; outHeight=0; prevWidth=0; prevHeight=0; } for (int i=0; igetXYZChannels(inX, inY, inZ); inWidth=inFrame->getWidth(); inHeight=inFrame->getHeight(); if (frame_no) { // save previous frame prevWidth=outWidth; prevHeight=outHeight; prevFrame=pfsio.createFrame(prevWidth, prevHeight); if (!optLuminanceMode) prevFrame->createXYZChannels(prevX, prevY, prevZ); else prevY=prevFrame->createChannel("Y"); int size=prevWidth*prevHeight; for (int i=0; ioutWidth) outWidth=inWidth; outHeight+=inHeight; justification (just, inWidth, prevWidth, outWidth, leftRight); lCol=leftRight[MIN]; rCol=leftRight[MAX]; tRow=outHeight-inHeight; bRow=outHeight-1; prevlCol=leftRight[PREV_MIN]; prevrCol=leftRight[PREV_MAX]; prevtRow=0; prevbRow=prevHeight-1; } else if (alignment==HORIZONTAL) { outWidth+=inWidth; if(inHeight>outHeight) outHeight=inHeight; justification (just, inHeight, prevHeight, outHeight, topBottom); lCol=outWidth-inWidth; rCol=outWidth-1; tRow=topBottom[MIN]; bRow=topBottom[MAX]; prevlCol=0; prevrCol=prevWidth-1; prevtRow=topBottom[PREV_MIN]; prevbRow=topBottom[PREV_MAX]; } if(inX != NULL) { //XYZ mode if (frame_no==0) optLuminanceMode=false; // first frame if(!optLuminanceMode) { outFrame = pfsio.createFrame(outWidth, outHeight); outFrame->createXYZChannels(outX, outY, outZ); for (int i=0; i=tRow && i<=bRow && j>=lCol && j<=rCol) { (*outX)(j,i)=(*inX)(j-lCol,i-tRow); (*outY)(j,i)=(*inY)(j-lCol,i-tRow); (*outZ)(j,i)=(*inZ)(j-lCol,i-tRow); } else if (i>=prevtRow && i<=prevbRow && j>=prevlCol && j<=prevrCol) { (*outX)(j,i)=(*prevX)(j-prevlCol,i-prevtRow); (*outY)(j,i)=(*prevY)(j-prevlCol,i-prevtRow); (*outZ)(j,i)=(*prevZ)(j-prevlCol,i-prevtRow); } else { (*outX)(j, i)=XYZ[X]; (*outY)(j, i)=XYZ[Y]; (*outZ)(j, i)=XYZ[Z]; } } } else { outFrame = pfsio.createFrame(prevWidth, prevHeight); outY=outFrame->createChannel("Y"); int size=prevWidth*prevHeight; for (int i=0; igetChannel("Y")) ) {// luminance mode if (frame_no==0) optLuminanceMode=true; // first frame if (optLuminanceMode) { outFrame = pfsio.createFrame(outWidth, outHeight); outY=outFrame->createChannel("Y"); for (int i=0; i=tRow && i<=bRow && j>=lCol && j<=rCol) (*outY)(j,i)=(*inY)(j-lCol,i-tRow); else if (i>=prevtRow && i<=prevbRow && j>=prevlCol && j<=prevrCol) (*outY)(j,i)=(*prevY)(j-prevlCol,i-prevtRow); else (*outY)(j, i)=luminance; } } else { outFrame = pfsio.createFrame(prevWidth, prevHeight); outFrame->createXYZChannels(outX, outY, outZ); int size=prevWidth*prevHeight; for (int i=0; i * * $Id: pfsrotate.cpp,v 1.1 2005/06/15 13:36:54 rafm Exp $ */ #include #include #include #include #include #include #define PROG_NAME "pfsrotate" class QuietException { }; void rotateArray( const pfs::Array2D *in, pfs::Array2D *out, bool clockwise ); void printHelp() { fprintf( stderr, PROG_NAME " [-r] [--help]\n" "See man page for more information.\n" ); } void rotateFrames( int argc, char* argv[] ) { pfs::DOMIO pfsio; static struct option cmdLineOptions[] = { { "help", no_argument, NULL, 'h' }, { "r", no_argument, NULL, 'r' }, { NULL, 0, NULL, 0 } }; bool clockwise = true; int xSize = -1; int ySize = -1; int optionIndex = 0; while( 1 ) { int c = getopt_long (argc, argv, "r", cmdLineOptions, &optionIndex); if( c == -1 ) break; switch( c ) { case 'h': printHelp(); throw QuietException(); case 'r': clockwise = false; break; case '?': throw QuietException(); case ':': throw QuietException(); } } bool firstFrame = true; pfs::Frame *resizedFrame = NULL; while( true ) { pfs::Frame *frame = pfsio.readFrame( stdin ); if( frame == NULL ) break; // No more frames pfs::Channel *X, *Y, *Z; frame->getXYZChannels( X, Y, Z ); pfs::Channel *dX, *dY, *dZ; if( firstFrame ) { xSize = frame->getHeight(); ySize = frame->getWidth(); resizedFrame = pfsio.createFrame( xSize, ySize ); firstFrame = false; } pfs::ChannelIterator *it = frame->getChannels(); while( it->hasNext() ) { pfs::Channel *originalCh = it->getNext(); pfs::Channel *newCh = resizedFrame->createChannel( originalCh->getName() ); rotateArray( originalCh, newCh, clockwise ); } pfs::copyTags( frame, resizedFrame ); pfsio.writeFrame( resizedFrame, stdout ); pfsio.freeFrame( frame ); } pfsio.freeFrame( resizedFrame ); } void rotateArray(const pfs::Array2D *in, pfs::Array2D *out, bool clockwise) { int outRows = out->getRows(); int outCols = out->getCols(); for( int i=0; i] [--right ] [--top ] [--bottom ] [--width ] [--height ] [--help] [x_ul y_ul x_br y_br] .SH DESCRIPTION Extract a rectangle out of each frame in PFS stream. You can either specify x and y coordinates of upper left and lower right corner (the coordinates start with 0 and rise in the left-to-right and up-to-botton directions) or give a combination of the options listed below. .SH OPTIONS .TP --left , -l Number of columns to be cut out from the left edge of an image. .TP --right , -r Number of columns to be cut out from the right edge of an image. .TP --top , -t Number of rows to be cut out from the top edge of an image. .TP --bottom , -b Number of rows to be cut out from the bottom edge of an image. .TP --width , -W Width of an output image. Note that --width can be mixed with either --left or --right option. .TP --height , -H Height of an output image. Note that --height can be mixed with either --top or --bottom option. .TP --help, -h Print a list of commandline options. .SH EXAMPLES .TP pfsin image.hdr | pfscut --left 20 --top 5 | pfsout out.hdr Cut out 20 columns from the left and 5 rows from the top edge of image.hdr and save frame as out.hdr. .TP pfsin image.hdr | pfscut --left 20 --width 400 | pfsout out.hdr Cut out 20 columns from the left edge of image.hdr, and create output image 400 pixels in width. .TP pfsin image.hdr | pfscut 0 0 511 511 | pfsout out.hdr Cut left-upper part of the image of the size 512x512 (note that coordinates start with 0 and 512 is the last row/column that is included in the resulting image). .TP .SH SEE ALSO .BR pfsin (1) .BR pfsout (1) .SH BUGS Please report bugs and comments to Dorota Zdrojewska . pfstools-2.2.0/src/filter/pfspanoramic.10000664000701400070140000000523114105165613016701 0ustar rkm38rkm38.TH "pfspanoramic" 1 .SH NAME pfspanoramic \- Perform projective transformations of spherical images .SH SYNOPSIS .B pfspanoramic + [--width ] [--height ] [--oversample ] [--interpolate] [--xrotate ] [--yrotate ] [--zrotate ] .SH DESCRIPTION Transform spherical maps between various projections. Currently .BI polar (latitude-longitude), .BI angular (light probe), .BI mirrorball and .BI cylindrical are supported. The syntax for specifying the transformation is .RI \| source_projection \|+\| target_projection \|, where .RI \| source_projection \| is the current mapping that source image uses and .RI \| target_projection \| is the projection you'd like it to be transformed to. If the projection has some optional parameters, you can specify them with syntax: //...+//... As of now only .BI angular supports a parameter - .BI angle - which defines how many degrees from the viewing direction the projection should cover, e.g. angular+angular/angle=180 converts angular image to show only half of a hemisphere around the viewing direction. .SH OPTIONS .TP --width , -w .TP --height , -h Make the target image respectively pixels wide and/or high. If only one is specified, the other is computed from the target projection's typical W/H ratio. If neither is specified, the width is taken from the source image and height is computed as above. .TP --oversample , -o Oversample each target pixel x times, improving quality in areas that are scaled down with respect to the source image. Reasonable values are 2 to 5, while setting it higher may make the reprojection unbearably slow. .TP --interpolate, -i Use bilinear interpolation when sampling the source image. Increases quality in magnified areas. .TP --xrotate , -x Rotate the spherical image degrees around X axis. .TP --yrotate , -y Rotate the spherical image degrees around Y axis. .TP --zrotate , -z Rotate the spherical image degrees around Z axis. .SH EXAMPLES .TP pfsin grace_probe.hdr | pfspanoramic angular+polar -i -o 3 -y 90 -w 500 | pfsout grace.hdr Transform grace angular map to polar (latitude-longitude) projection applying bilinear interpolation and 3x3 oversampling, while rotating it by 90 degrees around Y axis. The image will be resized to 500x250 pixels (as the polar projection has 2:1 width-to-height ratio) and finally saved in grace.hdr. .SH "SEE ALSO" .BR pfsin (1) .BR pfsout (1) .SH BUGS Please report bugs and comments to Miloslaw Smyk . pfstools-2.2.0/src/filter/pfsextractchannels.cpp0000664000701400070140000000717314105165613020547 0ustar rkm38rkm38/** * @brief Extract selected channels from pfs stream * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Rafal Mantiuk and Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Rafal Mantiuk, * * $Id: pfsextractchannels.cpp,v 1.2 2014/04/05 22:04:12 rafm Exp $ */ #include #include #include #include #include #include #define PROG_NAME "pfsextractchannels" class QuietException { }; void printHelp() { fprintf( stderr, PROG_NAME " channel_name [Channel_name...] [--help]\n" "See man page for more information.\n" ); } static void errorCheck( bool condition, const char *string ) { if( !condition ) { fprintf( stderr, PROG_NAME " error: %s\n", string ); throw QuietException(); } } struct ltstr { bool operator()(const char* s1, const char* s2) const { return strcmp(s1, s2) < 0; } }; void extractChannels( int argc, char* argv[] ) { pfs::DOMIO pfsio; static struct option cmdLineOptions[] = { { "help", no_argument, NULL, 'h' }, { NULL, 0, NULL, 0 } }; std::set keepChannels; int optionIndex = 0; while( 1 ) { int c = getopt_long (argc, argv, "h", cmdLineOptions, &optionIndex); if( c == -1 ) break; switch( c ) { case 'h': printHelp(); throw QuietException(); case '?': throw QuietException(); case ':': throw QuietException(); } } errorCheck( optind < argc, "At least one channel must be specified" ); for( ;optind < argc; optind++ ) keepChannels.insert( argv[optind] ); while( true ) { pfs::Frame *frame = pfsio.readFrame( stdin ); if( frame == NULL ) break; // No more frames { // Check if the listed channels exist std::set::iterator it; for( it = keepChannels.begin(); it != keepChannels.end(); it++ ) if( frame->getChannel( *it ) == NULL ) { fprintf( stderr, PROG_NAME " error: Channel %s does not exist\n", *it ); throw QuietException(); } } { // Remoe not listed channels pfs::ChannelIterator *it = frame->getChannels(); while( it->hasNext() ) { pfs::Channel *channel = it->getNext(); if( keepChannels.find(channel->getName() ) == keepChannels.end() ) frame->removeChannel( channel ); } } pfsio.writeFrame( frame, stdout ); pfsio.freeFrame( frame ); } } int main( int argc, char* argv[] ) { try { extractChannels( argc, argv ); } catch( pfs::Exception ex ) { fprintf( stderr, PROG_NAME " error: %s\n", ex.getMessage() ); return EXIT_FAILURE; } catch( QuietException ) { return EXIT_FAILURE; } return EXIT_SUCCESS; } pfstools-2.2.0/src/filter/pfspad.cpp0000664000701400070140000001763314105165613016127 0ustar rkm38rkm38/** * @brief Add borders to images in PFS stream * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Rafal Mantiuk and Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Dorota Zdrojewska, * */ #include #include #include #include #include #define PROG_NAME "pfspad" #define UNSP INT_MAX #define MIN 0 #define MAX 1 #define SIZE 2 #define X 0 #define Y 1 #define Z 2 using namespace std; class QuietException { }; static struct option cmdLineOptions[] = { {"help", no_argument, NULL, 'h'}, {"left", required_argument, NULL, 'l'}, {"right", required_argument, NULL, 'r'}, {"top", required_argument, NULL, 't'}, {"bottom", required_argument, NULL, 'b'}, {"width", required_argument, NULL, 'W'}, {"height", required_argument, NULL, 'H'}, {"R", required_argument, NULL, 'R'}, {"G", required_argument, NULL, 'G'}, {"B", required_argument, NULL, 'B'}, {"Y", required_argument, NULL, 'Y'}, {NULL, 0, NULL, 0} }; void printHelp() { fprintf(stderr, PROG_NAME ":\n" "\t[--left ]\n" "\t[--right ]\n" "\t[--top ]\n" "\t[--bottom ]\n" "\t[--width ]\n" "\t[--height \n" "\t[--R \n" "\t[--G \n" "\t[--B \n" "\t[--Y \n" "\t[--help]\n" "See man page for more information.\n"); } void calcBorders (int min, int max, int size, int inSize, int flag, int *out) { if (min<0 || max<0) throw pfs::Exception("You cannot add negative number of pixels"); if (sizemax) ? max : val; return out; } void pfspad (int argc, char* argv[]) { pfs::DOMIO pfsio; int left=UNSP, right=UNSP, top=UNSP, bottom=UNSP, //size of borders (in pixels) width=UNSP, height=UNSP; // size of an output image float R=1e-4, G=1e-4, B=1e-4, luminance=1.0; // color of borders in RGB or luminance mode float XYZ[3]; bool optLuminanceMode=false, RGBMode=false; int optionIndex=0; while (1) { int c=getopt_long(argc, argv, "hl:r:t:b:W:H:R:G:B:Y:", cmdLineOptions, &optionIndex); if(c==-1) break; switch(c) { case 'h': printHelp(); throw QuietException(); break; case 'l': left=atoi(optarg); break; case 'r': right=atoi(optarg); break; case 't': top=atoi(optarg); break; case 'b': bottom=atoi(optarg); break; case 'W': width=atoi(optarg); if (width<=0) throw pfs::Exception("Size of an output image cannot be negative or equal to 0"); break; case 'H': height=atoi(optarg); if (height<=0) throw pfs::Exception("Size of an output image cannot be negative or equal to 0"); break; case 'R': R=atof(optarg); RGBMode=true; if(R<=(1e-4)) R=1e-4; break; case 'G': G=atof(optarg); RGBMode=true; if(G<=(1e-4)) G=1e-4; break; case 'B': B=atof(optarg); RGBMode=true; if(B<=(1e-4)) B=1e-4; break; case 'Y': luminance=atof(optarg); optLuminanceMode = true; if(luminance<=(1e-4)) luminance=1e-4; break; } } if(optLuminanceMode && !RGBMode) { R=1; G=1; B=1; } //transform RGB colors to YXZ color space and multiply by luminance XYZ[X]=luminance*(0.4124*R + 0.3576*G + 0.1805*B); XYZ[Y]=luminance*(0.2126*R + 0.7152*G + 0.0722*B); XYZ[Z]=luminance*(0.0193*R + 0.1192*G + 0.9505*B); XYZ[X]=clamp(XYZ[X],1e-4,100000000.0); XYZ[Y]=clamp(XYZ[Y],1e-4,100000000.0); XYZ[Z]=clamp(XYZ[Z],1e-4,100000000.0); pfs::Channel *inX, *inY, *inZ, *outX, *outY, *outZ; while (1) { pfs::Frame *inFrame = pfsio.readFrame(stdin); if (inFrame==NULL) break; // no more frames inFrame->getXYZChannels(inX, inY, inZ); int inWidth=inFrame->getWidth(); int inHeight=inFrame->getHeight(); //calculate borders to add int leftRight[3], topBottom[3]; calcBorders(left, right, width, inWidth, 1, leftRight); calcBorders(top, bottom, height, inHeight, 0, topBottom); int lCol=leftRight[MIN]; int rCol=leftRight[MAX]; int tRow=topBottom[MIN]; int bRow=topBottom[MAX]; int outWidth=leftRight[SIZE]; int outHeight=topBottom[SIZE]; pfs::Frame *outFrame = pfsio.createFrame(outWidth, outHeight); if (inX != NULL) { //XYZ mode outFrame->createXYZChannels(outX, outY, outZ); for (int i=0; i=tRow && i<=bRow && j>=lCol && j<=rCol) { (*outX)(j, i)=(*inX)(j-lCol,i-tRow); (*outY)(j, i)=(*inY)(j-lCol,i-tRow); (*outZ)(j, i)=(*inZ)(j-lCol,i-tRow); } else { (*outX)(j, i)=XYZ[X]; (*outY)(j, i)=XYZ[Y]; (*outZ)(j, i)=XYZ[Z]; } } else if ( (inY=inFrame->getChannel("Y")) != NULL ) { // only luminance outY=outFrame->createChannel("Y"); for (int i=0; i=tRow && i<=bRow && j>=lCol && j<=rCol) (*outY)(j, i)=(*inY)(j-lCol,i-tRow); else (*outY)(j, i)=luminance; } else throw pfs::Exception("Missing X, Y, Z channels in the PFS stream"); pfs::copyTags(inFrame, outFrame); pfsio.writeFrame(outFrame, stdout); pfsio.freeFrame(inFrame); pfsio.freeFrame(outFrame); } } int main (int argc, char* argv[]) { try { pfspad(argc, argv); } catch (pfs::Exception ex) { fprintf (stderr, PROG_NAME " error: %s\n", ex.getMessage()); return EXIT_FAILURE; } catch (QuietException ex) { return EXIT_FAILURE; } return EXIT_SUCCESS; } pfstools-2.2.0/src/filter/pfscolortransform.10000664000701400070140000000351014105165613020000 0ustar rkm38rkm38.TH "pfscolortransform" 1 .SH NAME pfscolortransform \- Apply color calibration using user provided matrix file. .SH SYNOPSIS .B pfscolortransform (--\fBxyzrgb\fR matrix-file | --\fBrgbxyz\fR matrix-file) [--\fBtranspose] .SH DESCRIPTION Use this command to calibrate colours using a calibration matrix supplied in a text file. The command is intended for linearized (not gamma corrected) color spaces. The matrix should be 3x3 and it should transform from CIE XYZ to ITU rec. 709 RGB (sRGB primaries but linear, no gamma) or from the same RGB to CIE XYZ (see options below). The text file should contain three rows with numbers separated by a comma, for example: .PP .PD 0 .IP 0.0292,0.0126,0.0009 .IP 0.0052,0.0340,-0.0049 .IP 0.0055,-0.0059,0.0409 .PD .PP If the --\fBxyzrgb\fR is supplied, the pixel values are color-transformed as follows: .IP [RGB]' = M * [XYZ] .PP where ' is matrix transposition, * is matrix multiplication, RGB is a vector of RGB values and XYZ is the vector of CIE XYZ trichromatic values. If the --\fBrgbxyz\fR is supplied, the pixel values are color-transformed as follows: .IP [XYZ]' = M * [RGB] .SH OPTIONS .TP --\fBxyzrgb\fR matrix-file, -\fBx\fR matrix-file User provided matrix is a conversion matrix to convert from XYZ to RGB color space. .TP --\fBrgbxyz\fR matrix-file, -\fBr\fR matrix-file User provided matrix is a conversion matrix to convert from RGB to XYZ color space. .TP --\fBtranspose, -\fBt Take transpose of provided matrix file. .SH EXAMPLES .TP pfsin memorial.hdr | pfscolortransform -x matrix.txt | pfsout memorial_gc.ppm .IP Color calibrate memorial image with matrix in matrix.txt file and save to memorial_gc.ppm. .SH "SEE ALSO" .BR pfsin (1) .BR pfsout (1) .BR pfsdisplayfunction (1) .SH BUGS Please report bugs and comments to the pfstools discussion group (http://groups.google.com/group/pfstools). pfstools-2.2.0/src/filter/pfsdisplayfunction.10000664000701400070140000001103414105165613020141 0ustar rkm38rkm38.TH "pfsdisplayfunction" 1 .SH NAME pfsdisplayfunction \- Apply display function to color or gray-scale images .SH SYNOPSIS .B pfsdisplayfunction [\fB--display-function\fR <\fIdf-spec\fR>] [--\fBto-pixels\fR | --\fBto-luminance\fR] [--\fBhelp\fR] [--\fBverbose\fR] .SH DESCRIPTION Use this command to convert pixel values to luminance / radiance units displayed on a monitor or to perform inverse conversion. This command is useful in combination with the HDR-VDP to convert LDR images to luminance maps representing images shown on a particular display. The display function specification is identical to the specification used in the \fIpfstmo_mantiuk08\fR tone mapping operator (from the pfstmo package). .PP If neither --\fBto-luminance\fR nor --\fBto-pixels\fR option is specified, the appropriate conversion direction will be deducted from the LUMINANCE tag in the pfs stream. .SH OPTIONS .TP --\fBto-luminance\fR, -\fBl\fR Convert pixel values to absolute luminance / radiance units. For RGB images the same display function is applied in each color channel. .TP --\fBto-pixels\fR, -\fBp\fR Convert absolute luminance / radiance units to pixel values. For RGB images the same display function is applied in each color channel. .TP \fB--display-function\fR <\fIdf-spec\fR>, \fB-d\fR <\fIdf-spec\fR> The display function describes how output luminance of a display changes with pixel values. If no parameter is given, the command assumes \fB-df\ pd=lcd\fR (see \fIPre-defined display\fR below). There are several ways to specify the display function: .TP \fIGamma-gain-black-ambient display model\fR .IP g=:l=:b=:k=:a=[:n=] .IP Gamma-gain-black-ambient model can approximate a range of displays and is a compact way to specify a display function. It assumes that a display function has the following form: .IP L_d(I) = (l-b)*I^gamma + b + k/pi*a .IP The parameters are as follows: .RS .PD 0 .TP 5 \fBg\fR - gamma or exponent of a display function (default 2.2, usually from 1.8 to 2.8) .TP 5 \fBl\fR - peak luminance of a display in cd/m^2 (default 100, from 80 for CRTs to 500 or more for newer displays) .TP 5 \fBb\fR - black level, which is luminance of a black pixel when the display is on (default 1, usually from 0.3 to 1 cd/m^2) .TP 5 \fBk\fR - reflectivity of a screen (assuming that it is diffuse) (default 0.01, usually about 0.01 (1%) for LCD displays, more for CRTs) .TP 5 \fBa\fR - ambient illumination in lux. Typical values are: .RS .IP 50\ lux Family living room (dim, \fBdefault\fR) .IP 400\ lux A brightly lit office .IP 32000\ lux Sunlight on an average day (min.) .IP 100000\ lux Sunlight on an average day (max.) .RE .RE .PD .TP \fIPre-defined display\fR .IP \fBpd\fR=\fI\fI .IP Use pre-defined display type. This options are for convenience only and they do not mean to accurately model the response of a particular display. The following \fIdisplay type\fRs are recognized: .RS .TP \fBlcd_office\fR (g=2.2, l=100, b=0.8, k=0.01, a=400 ) lcd set to "office" mode seen in bright environment .PD 0 .TP \fBlcd\fR (g=2.2, l=200, b=0.8, k=0.01, a=60 ) typical lcd seen in dim environment (\fBdefault\fR) .TP \fBlcd_bright\fR (g=2.6, l=500, b=0.5, k=0.01, a=10 ) newer LCD TV seen in dark environment .TP \fBcrt\fR (g=2.2, l=80, b=1, k=0.02, a=60 ) CRT monitor seen in dim environment .PD .RE .IP The parameters in the parenthesis are the same as for the gamma-gain-black-ambient model explained above. .TP \fILookup-table\fR .IP \fBlut\fR=\fI\fI .IP This is the most accurate specification of the display response function, but requires measuring it with a luminance meter. The lookup table should account also for ambient light, so that it is recommended to use the luminance meter that can measure screen luminance from a distance, such as Minolta LS-100 (as opposed to those that use rubber tube touching a display that eliminates the influence of ambient light). The must be a comma-separated text file in a format (CSV) with two columns: first column represents pixel values (from 0.0 to 1.0) and the second physical luminance in cd/m^2. Both the pixel value and the luminance should increase in each raw. .SH EXAMPLES .TP pfsin barbara.jpg | pfsdisplayfunction -l -d pd:crt -v | pfsout barbara_crt.hdr .IP Convert barbara.jpg image from pixel values to a luminance map of the image shown on a CRT monitor and store the map as an HDR image. .SH "SEE ALSO" .BR pfsgamma (1) .BR pfstmo_mantiuk08 (1) .SH BUGS Please report bugs and comments to the pfstools discussion group (http://groups.google.com/group/pfstools). pfstools-2.2.0/src/filter/pfssize.cpp0000664000701400070140000002735114105165613016333 0ustar rkm38rkm38/** * @brief Resize images in PFS stream * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Rafal Mantiuk and Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Rafal Mantiuk, * * $Id: pfssize.cpp,v 1.4 2009/01/29 00:44:30 rafm Exp $ */ #include #include #include #include #include #include #include "config.h" #define PROG_NAME "pfssize" #define ROUNDING_ERROR 0.000001 class QuietException { }; class ResampleFilter { public: /** * Size of the filter in samples. */ virtual float getSize() = 0; /** * Get value of the filter for x. x is always positive. */ virtual float getValue( const float x ) = 0; virtual ~ResampleFilter() { } }; void resize( const pfs::Array2D *src, pfs::Array2D *dest ); void resampleMitchell( const pfs::Array2D *in, pfs::Array2D *out ); void resampleArray( const pfs::Array2D *in, pfs::Array2D *out, ResampleFilter *filter ); inline float max( float a, float b ) { return a > b ? a : b; } inline float min( float a, float b ) { return a < b ? a : b; } // --------- Filters -------- class MitchellFilter : public ResampleFilter { public: float getSize() { return 2; } float getValue( const float x ) { const float B = 0.3333f, C = 0.3333f; const float x2 = x*x; if( x < 1 ) return 1.f/6.f * ( (12-9*B-6*C)*x2*x + (-18+12*B+6*C)*x2 + (6-2*B) ); if( x >=1 && x < 2 ) return 1.f/6.f * ( (-B-6*C)*x2*x + (6*B + 30*C)*x2 + (-12*B-48*C)*x + (8*B+24*C) ); return 0; } }; class LinearFilter : public ResampleFilter { public: float getSize() { return 1; } float getValue( const float x ) { if( x < 1 ) return 1 - x; return 0; } }; class BoxFilter : public ResampleFilter { public: float getSize() { return 0.5; } float getValue( const float x ) { return 1; } }; void printHelp() { fprintf( stderr, PROG_NAME " [--x ] [--y ] [--ratio ] [--verbose] [--help]\n" "See man page for more information.\n" ); } static void errorCheck( bool condition, const char *string ) { if( !condition ) { fprintf( stderr, PROG_NAME " error: %s\n", string ); throw QuietException(); } } static int getIntParam( const char *str, const char *paramName ) { char *endp; int val = strtol( optarg, &endp, 10 ); if( endp == optarg ) { std::ostringstream oss; oss << "Bad value for " << paramName << " argument"; throw pfs::Exception( oss.str().c_str() ); } return val; } static float getFloatParam( const char *str, const char *paramName ) { char *endp; float val = strtof( optarg, &endp ); if( endp == optarg ) { std::ostringstream oss; oss << "Bad value for " << paramName << " argument"; throw pfs::Exception( oss.str().c_str() ); } return val; } void resizeFrames( int argc, char* argv[] ) { pfs::DOMIO pfsio; float ratio = -1; int xSize = -1; int ySize = -1; int minX = -1; int maxX = -1; int minY = -1; int maxY = -1; bool verbose = false; ResampleFilter *filter = NULL; static struct option cmdLineOptions[] = { { "help", no_argument, NULL, 'h' }, { "verbose", no_argument, NULL, 'v' }, { "x", required_argument, NULL, 'x' }, { "y", required_argument, NULL, 'y' }, { "maxx", required_argument, NULL, '1' }, { "maxy", required_argument, NULL, '2' }, { "minx", required_argument, NULL, '3' }, { "miny", required_argument, NULL, '4' }, { "ratio", required_argument, NULL, 'r' }, { "filter", required_argument, NULL, 'f' }, { NULL, 0, NULL, 0 } }; int optionIndex = 0; while( 1 ) { int c = getopt_long (argc, argv, "x:y:r:f:", cmdLineOptions, &optionIndex); if( c == -1 ) break; switch( c ) { case 'h': printHelp(); throw QuietException(); case 'v': verbose = true; break; case 'x': xSize = getIntParam( optarg, "x" ); break; case 'y': ySize = getIntParam( optarg, "y" ); break; case 'r': ratio = getFloatParam( optarg, "ratio" ); break; case '1': maxX = getIntParam( optarg, "maxx" ); break; case '2': maxY = getIntParam( optarg, "maxy" ); break; case '3': minX = getIntParam( optarg, "minx" ); break; case '4': minY = getIntParam( optarg, "miny" ); break; case 'f': if( !strcasecmp( optarg, "LINEAR" ) ) { filter = new LinearFilter(); } else if( !strcasecmp( optarg, "MITCHELL" ) ) { filter = new MitchellFilter(); } else if( !strcasecmp( optarg, "BOX" ) ) { filter = new BoxFilter(); } else { throw pfs::Exception( "Unknown filter. Possible values: LINEAR, BOX, MITCHELL" ); } break; case '?': throw QuietException(); case ':': throw QuietException(); } } if( filter == NULL ) filter = new LinearFilter(); bool isMinMax = (minX!=-1) || (maxX!=-1) || (minY!=-1) || (maxY!=-1); errorCheck( (minX==-1 || maxX == -1 || minX <= maxX) && (minY==-1 || maxY==-1 || minY <= maxY), "Min value must be lower than max value" ); errorCheck( (ratio != -1) ^ (xSize != -1 || ySize != -1) ^ isMinMax, "Specify either size or ratio or min/max sizes" ); errorCheck( ratio == -1 || ratio > 0 , "Wrong scaling ratio" ); // bool firstFrame = true; pfs::Frame *resizedFrame = NULL; while( true ) { pfs::Frame *frame = pfsio.readFrame( stdin ); if( frame == NULL ) break; // No more frames pfs::Channel *X, *Y, *Z; frame->getXYZChannels( X, Y, Z ); // pfs::Channel *dX, *dY, *dZ; // if( firstFrame ) { int new_x, new_y; if( ratio != -1 ) { new_x = (int)(frame->getWidth()*ratio); new_y = (int)(frame->getHeight()*ratio); } else { if( isMinMax ) { // Min/max sizes given new_x = frame->getWidth(); new_y = frame->getHeight(); float mm_ratio = (float)new_x/(float)new_y; if( minX != -1 && new_x < minX ) { new_x = minX; new_y = (int)((float)new_x/mm_ratio); } if( minY != -1 && new_y < minY ) { new_y = minY; new_x = (int)((float)new_y*mm_ratio); } if( maxX != -1 && new_x > maxX ) { new_x = maxX; new_y = (int)((float)new_x/mm_ratio); } if( maxY != -1 && new_y > maxY ) { new_y = maxY; new_x = (int)((float)new_y*mm_ratio); } } else { // Size given new_x = xSize; new_y = ySize; if( new_x == -1 ) new_x = (int)((float)frame->getWidth() * (float)ySize / (float)frame->getHeight()); else if( new_y == -1 ) new_y = (int)((float)frame->getHeight() * (float)xSize / (float)frame->getWidth()); } } errorCheck( new_x > 0 && new_y > 0 && new_x <= 65536 && new_y <= 65536, "Wrong frame size" ); errorCheck( ((frame->getWidth() <= new_x) && (frame->getHeight() <= new_y)) || ((frame->getWidth() >= new_x) && (frame->getHeight() >= new_y)), "Can upsample / downsample image only in both dimensions simultaneously" ); if( verbose ) fprintf( stderr, "New size: %d x %d \n", new_x, new_y ); resizedFrame = pfsio.createFrame( new_x, new_y ); // firstFrame = false; // } pfs::ChannelIterator *it = frame->getChannels(); while( it->hasNext() ) { pfs::Channel *originalCh = it->getNext(); pfs::Channel *newCh = resizedFrame->createChannel( originalCh->getName() ); resampleArray( originalCh, newCh, filter ); } pfs::copyTags( frame, resizedFrame ); pfsio.writeFrame( resizedFrame, stdout ); pfsio.freeFrame( frame ); pfsio.freeFrame( resizedFrame ); } delete filter; } void upsampleArray( const pfs::Array2D *in, pfs::Array2D *out, ResampleFilter *filter ) { float dx = (float)in->getCols() / (float)out->getCols(); float dy = (float)in->getRows() / (float)out->getRows(); float pad; float filterSamplingX = max( modff( dx, &pad ), 0.01f ); float filterSamplingY = max( modff( dy, &pad ), 0.01f ); const int outRows = out->getRows(); const int outCols = out->getCols(); const float inRows = (float)in->getRows(); const float inCols = (float)in->getCols(); const float filterSize = filter->getSize(); // TODO: possible optimization: create lookup table for the filter float sx, sy; int x, y; for( y = 0, sy = -0.5f + dy/2; y < outRows; y++, sy += dy ) for( x = 0, sx = -0.5f + dx/2; x < outCols; x++, sx += dx ) { float pixVal = 0; float weight = 0; for( float ix = max( 0, ceilf( sx-filterSize ) ); ix <= min( floorf(sx+filterSize), inCols-1 ); ix++ ) for( float iy = max( 0, ceilf( sy-filterSize ) ); iy <= min( floorf( sy+filterSize), inRows-1 ); iy++ ) { float fx = fabs( sx - ix ); float fy = fabs( sy - iy ); const float fval = filter->getValue( fx )*filter->getValue( fy ); pixVal += (*in)( (int)ix, (int)iy ) * fval; weight += fval; } if( weight == 0 ) { fprintf( stderr, "%g %g %g %g\n", sx, sy, dx, dy ); } // assert( weight != 0 ); (*out)(x,y) = pixVal / weight; } } void downsampleArray( const pfs::Array2D *in, pfs::Array2D *out ) { const float inRows = (float)in->getRows(); const float inCols = (float)in->getCols(); const int outRows = out->getRows(); const int outCols = out->getCols(); const float dx = (float)in->getCols() / (float)out->getCols(); const float dy = (float)in->getRows() / (float)out->getRows(); const float filterSize = 0.5; float sx, sy; int x, y; for( y = 0, sy = dy/2-0.5f; y < outRows; y++, sy += dy ) for( x = 0, sx = dx/2-0.5f; x < outCols; x++, sx += dx ) { float pixVal = 0; float w = 0; for( float ix = max( 0, ceilf( sx-dx*filterSize ) ); ix <= min( floorf( sx+dx*filterSize ), inCols-1 ); ix++ ) for( float iy = max( 0, ceilf( sy-dx*filterSize ) ); iy <= min( floorf( sy+dx*filterSize), inRows-1 ); iy++ ) { pixVal += (*in)( (int)ix, (int)iy ); w += 1; } (*out)(x,y) = pixVal/w; } } void resampleArray( const pfs::Array2D *in, pfs::Array2D *out, ResampleFilter *filter ) { if( in->getCols() == out->getCols() && in->getRows() == out->getRows() ) pfs::copyArray( in, out ); else if( in->getCols() < out->getCols() || in->getRows() < out->getRows() ) upsampleArray( in, out, filter ); else downsampleArray( in, out ); } int main( int argc, char* argv[] ) { try { resizeFrames( argc, argv ); } catch( pfs::Exception ex ) { fprintf( stderr, PROG_NAME " error: %s\n", ex.getMessage() ); return EXIT_FAILURE; } catch( QuietException ) { return EXIT_FAILURE; } return EXIT_SUCCESS; } pfstools-2.2.0/src/filter/pfsabsolute.cpp0000664000701400070140000001120514105165613017166 0ustar rkm38rkm38/** * @brief Convert luminance in images to absolute measure * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Rafal Mantiuk and Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Rafal Mantiuk, * * $Id: pfsabsolute.cpp,v 1.3 2006/08/14 16:48:31 rafm Exp $ */ #include #include #include #include #include #include #include #define PROG_NAME "pfsabsolute" class QuietException { }; inline void multiplyArray(pfs::Array2D *z, const pfs::Array2D *x, const float y) { assert( x->getRows() == z->getRows() ); assert( x->getCols() == z->getCols() ); if( y == 1.0f ) return; const int elements = x->getRows()*x->getCols(); for( int i = 0; i < elements; i++ ) (*z)(i) = (*x)(i) * y; } void printHelp() { fprintf( stderr, PROG_NAME " [] [--verbose] [--help]\n" "See man page for more information.\n" ); } void applyAbsoluteOnFrames( int argc, char* argv[] ) { pfs::DOMIO pfsio; float destY = 1.0f; float srcY = 1.0f; bool verbose = false; static struct option cmdLineOptions[] = { { "help", no_argument, NULL, 'h' }, { "verbose", no_argument, NULL, 'v' }, { NULL, 0, NULL, 0 } }; int optionIndex = 0; while( 1 ) { int c = getopt_long (argc, argv, "", cmdLineOptions, &optionIndex); if( c == -1 ) break; switch( c ) { case 'h': printHelp(); throw QuietException(); case 'v': verbose = true; break; case '?': throw QuietException(); case ':': throw QuietException(); } } if( optind == argc ) throw pfs::Exception( "Destination luminance level must be specified" ); if( optind < (argc - 2) ) throw pfs::Exception( "Too many arguments" ); destY = strtof( argv[optind++], NULL ); if( optind != argc ) srcY = strtof( argv[optind++], NULL ); VERBOSE_STR << "rescale luminance to: " << destY << std::endl; if( srcY != 1.0f ) VERBOSE_STR << "from: " << srcY << std::endl; float multY = destY/srcY; while( true ) { pfs::Frame *frame = pfsio.readFrame( stdin ); if( frame == NULL ) break; // No more frames const char *lumType = frame->getTags()->getString( "LUMINANCE" ); if( lumType != NULL && !strcmp( lumType, "ABSOLUTE" ) ) { VERBOSE_STR << "luminance is already absolute, skipping frame."; } else { pfs::Channel *X, *Y, *Z; frame->getXYZChannels( X, Y, Z ); if( X != NULL ) { // Color, XYZ if( lumType != NULL && !strcmp( lumType, "DISPLAY" ) ) { VERBOSE_STR << "converting from display-referred to linear luminance."; pfs::transformColorSpace( pfs::CS_XYZ, X, Y, Z, pfs::CS_RGB, X, Y, Z ); pfs::transformColorSpace( pfs::CS_SRGB, X, Y, Z, pfs::CS_XYZ, X, Y, Z ); } multiplyArray( X, X, multY ); multiplyArray( Y, Y, multY ); multiplyArray( Z, Z, multY ); } else if( (Y = frame->getChannel( "Y" )) != NULL ) { // Luminance only if( lumType != NULL && !strcmp( lumType, "DISPLAY" ) ) throw pfs::Exception( PROG_NAME ": Cannot handle gray-level display-referred images." ); multiplyArray( Y, Y, multY ); } else throw pfs::Exception( "Missing color channels in the PFS stream" ); frame->getTags()->setString("LUMINANCE", "ABSOLUTE"); } pfsio.writeFrame( frame, stdout ); pfsio.freeFrame( frame ); } } int main( int argc, char* argv[] ) { try { applyAbsoluteOnFrames( argc, argv ); } catch( pfs::Exception ex ) { fprintf( stderr, PROG_NAME " error: %s\n", ex.getMessage() ); return EXIT_FAILURE; } catch( QuietException ex ) { return EXIT_FAILURE; } return EXIT_SUCCESS; } pfstools-2.2.0/src/filter/pfspanoramic.cpp0000664000701400070140000005165014105165613017331 0ustar rkm38rkm38/** * @brief Perform projective transformations of spherical images * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Rafal Mantiuk and Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Miloslaw Smyk, * * $Id: pfspanoramic.cpp,v 1.5 2014/04/05 22:04:12 rafm Exp $ */ #include #include #include #include #include #include #include #define PROG_NAME "pfspanoramic" using namespace std; class QuietException { }; const double EPSILON=1e-7; const double PI = 3.1415926535897932384626433; const double ONE_PI = 0.31830988618379067154; // 1/PI static void errorCheck( bool condition, const char *string ) { if( !condition ) { fprintf( stderr, PROG_NAME " error: %s\n", string ); throw QuietException(); } } class Vector3D { public: double x, y, z; Vector3D(double phi, double theta) { x = cos(phi) * sin(theta); y = sin(phi) * sin(theta); z = cos(theta); } Vector3D(double x, double y, double z) { this->x = x; this->y = y; this->z = z; normalize(); } double magnitude(void) { return sqrt( x * x + y * y + z * z ); } void normalize(void) { double len = magnitude(); x = x / len; y = y / len; z = z / len; } double dot(Vector3D *v) { return x * v->x + y * v->y + z * v->z; } //TODO: optimize rotations by precomputing sines and cosines void rotateX(double angle) { angle *= (PI / 180.); double c = cos(angle); double s = sin(angle); double y2 = c * y + -s * z; double z2 = s * y + c * z; y = y2; z = z2; } void rotateY(double angle) { angle *= (PI / 180.); double c = cos(angle); double s = sin(angle); double x2 = c * x + s * z; double z2 = -s * x + c * z; x = x2; z = z2; } void rotateZ(double angle) { angle *= (PI / 180); double c = cos(angle); double s = sin(angle); double x2 = c * x + -s * y; double y2 = s * x + c * y; x = x2; y = y2; } }; class Point2D { public: double x, y; Point2D(double x, double y) { this->x = x; this->y = y; } }; class Projection { protected: const char *name; public: virtual Vector3D *uvToDirection(double u, double v) = 0; virtual Point2D *directionToUV(Vector3D *direction) = 0; virtual bool isValidPixel(double u, double v) = 0; virtual double getSizeRatio(void) = 0; virtual ~Projection() { } virtual void setOptions(char *opts) { } virtual const char *getName(void) { return name; } }; //class Projection; typedef Projection*(*ProjectionCreator)(void); class ProjectionFactory { static ProjectionFactory singleton; ProjectionFactory(bool init) { } public: map projections; static void registerProjection(const char *name, ProjectionCreator ptr) { singleton.projections[ string( name ) ] = ptr; } static Projection *getProjection(char *name) { char *opts; if( (opts = strchr(name, '/')) ) { *opts++ = '\0'; } ProjectionCreator projectionCreator = singleton.projections.find(string(name))->second; if(projectionCreator != NULL) { Projection *projection = projectionCreator(); if(opts != NULL) projection->setOptions(opts); return projection; } else return NULL; } //FIXME: Lame. Should return an iterator over the names. No time for this now. :/ static void listProjectionNames(void) { map::iterator i = singleton.projections.begin(); while(i != singleton.projections.end()) { fprintf( stderr, "%s\n", (*i).first.c_str()); i++; } } }; ProjectionFactory ProjectionFactory::singleton = true; class MirrorBallProjection : public Projection { static MirrorBallProjection singleton; MirrorBallProjection(bool initialization) { name = "mirrorball"; if(initialization) ProjectionFactory::registerProjection(name, this->create); } public: static Projection* create() { return new MirrorBallProjection(false); } const char *getName(void) { return name; } double getSizeRatio(void) { return 1; } bool isValidPixel(double u, double v) { // check if we are not in a boundary region (outside a circle) if((u - 0.5) * (u - 0.5) + (v - 0.5) * (v - 0.5) > 0.25) return false; else return true; } Vector3D* uvToDirection(double u, double v) { u = 2 * u - 1; v = 2 * v - 1; double phi = atan2( v, u ); double theta = 2 * asin( sqrt( u * u + v * v ) ); Vector3D *direction = new Vector3D(phi, theta); double t; direction->y = -direction->y; return direction; } Point2D* directionToUV(Vector3D *direction) { double u, v; direction->y = -direction->y; if(fabs(direction->x) > 0 || fabs(direction->y) > 0) { double distance = sqrt(direction->x * direction->x + direction->y * direction->y); double r = 0.5 * (sin(acos(direction->z) / 2)) / distance; u = direction->x * r + 0.5; v = direction->y * r + 0.5; } else { u = v = 0.5; } return new Point2D(u, v); } }; class AngularProjection : public Projection { static AngularProjection singleton; double totalAngle; AngularProjection(bool initialization) { name = "angular"; if(initialization) ProjectionFactory::registerProjection(name, this->create); } public: static Projection* create() { AngularProjection *p = new AngularProjection(false); p->totalAngle = 360; return (Projection *)p; } void setOptions(char *opts) { char *delimiter; static const char *OPTION_ANGLE = "angle"; while(*opts) { //fprintf(stderr,"option: %s\n", opts); //if(delimiter = strchr(name, '/')) //*delimiter++ = '\0'; if(strncmp(opts, OPTION_ANGLE, strlen(OPTION_ANGLE)) == 0) { totalAngle = strtod(opts + strlen(OPTION_ANGLE) + 1, &delimiter); // fprintf(stderr,"angle: %g\n", totalAngle); if(0 >= totalAngle || totalAngle > 360) { fprintf( stderr, PROG_NAME " error: angular projection: angle must be in (0,360] degrees range.\n" ); throw QuietException(); } } else { fprintf( stderr, PROG_NAME " error: angular projection: unknown option: %s\n", opts ); throw QuietException(); } opts = delimiter + 1; } } const char *getName(void) { return name; } double getSizeRatio(void) { return 1; } bool isValidPixel(double u, double v) { // check if we are not in a boundary region (outside a circle) if((u - 0.5) * (u - 0.5) + (v - 0.5) * (v - 0.5) > 0.25) return false; else return true; } Vector3D* uvToDirection(double u, double v) { u = 2 * u - 1; v = 2 * v - 1; u *= totalAngle / 360; v *= totalAngle / 360; double phi = atan2( v, u ); double theta = PI * sqrt( u * u + v * v ); Vector3D *direction = new Vector3D(phi, theta); double t; direction->y = -direction->y; return direction; } Point2D* directionToUV(Vector3D *direction) { double u, v; direction->y = -direction->y; if(fabs(direction->x) > 0 || fabs(direction->y) > 0) { double distance = sqrt(direction->x * direction->x + direction->y * direction->y); double r = (1 / (2 * PI)) * acos(direction->z) / distance; u = direction->x * r + 0.5; v = direction->y * r + 0.5; } else { u = v = 0.5; } return new Point2D(u, v); } }; class CylindricalProjection : public Projection { Vector3D *pole; Vector3D *equator; Vector3D *cross; static CylindricalProjection singleton; CylindricalProjection(bool initialization) { name = "cylindrical"; if(initialization) ProjectionFactory::registerProjection(name, this->create); pole = new Vector3D(0, 1, 0); equator = new Vector3D(0, 0, -1); cross = new Vector3D(1, 0, 0); } public: static Projection* create() { return new CylindricalProjection(false); } ~CylindricalProjection() { delete pole; delete equator; delete cross; } double getSizeRatio(void) { return 2; } bool isValidPixel(double u, double v) { return true; } Vector3D* uvToDirection(double u, double v) { u = 0.75 - u; u *= PI * 2; v = acos( 1 - 2 * v ); Vector3D *direction = new Vector3D(u, v); double temp = direction->z; direction->z = direction->y; direction->y = temp; return direction; } Point2D* directionToUV(Vector3D *direction) { double u, v; double lat = direction->dot(pole); v = ( 1 - lat ) / 2; if(v < EPSILON || fabs(1 - v) < EPSILON) u = 0; else { double ratio = equator->dot( direction ) / sin( acos( lat ) ); if(ratio < -1) ratio = -1; else if(ratio > 1) ratio = 1; double lon = acos(ratio) / (2 * PI); if(cross->dot(direction) < 0) u = lon; else u = 1 - lon; if(u == 1) u = 0; if(v == 1) v = 0; } // if ( 0 > v || v >= 1 ) fprintf(stderr, "u: %f (%f,%f,%f)\n", v, direction->x, direction->y, direction->z); // assert ( -0. <= u && u < 1 ); // assert ( -0. <= v && v < 1 ); return new Point2D(u, v); } }; class PolarProjection : public Projection { Vector3D *pole; Vector3D *equator; Vector3D *cross; static PolarProjection singleton; PolarProjection(bool initialization) { name = "polar"; if(initialization) ProjectionFactory::registerProjection(name, this->create); pole = new Vector3D(0, 1, 0); equator = new Vector3D(0, 0, -1); cross = new Vector3D(1, 0, 0); } public: static Projection* create() { return new PolarProjection(false); } ~PolarProjection() { delete pole; delete equator; delete cross; } double getSizeRatio(void) { return 2; } bool isValidPixel(double u, double v) { return true; } Vector3D* uvToDirection(double u, double v) { u = 0.75 - u; u *= PI * 2; v *= PI; Vector3D *direction = new Vector3D(u, v); double temp = direction->z; direction->z = direction->y; direction->y = temp; return direction; } Point2D* directionToUV(Vector3D *direction) { double u, v; double lat = acos(direction->dot(pole)); v = lat * ONE_PI; if(v < EPSILON || fabs(1 - v) < EPSILON) u = 0; else { double ratio = equator->dot(direction) / sin(lat); if(ratio < -1) ratio = -1; else if(ratio > 1) ratio = 1; double lon = acos(ratio) / (2 * PI); if(cross->dot(direction) < 0) u = lon; else u = 1 - lon; if(u == 1) u = 0; if(v == 1) v = 0; } // if ( 0 > v || v >= 1 ) fprintf(stderr, "u: %f (%f,%f,%f)\n", v, direction->x, direction->y, direction->z); // assert ( -0. <= u && u < 1 ); // assert ( -0. <= v && v < 1 ); return new Point2D(u, v); } }; PolarProjection PolarProjection::singleton = true; CylindricalProjection CylindricalProjection::singleton = true; AngularProjection AngularProjection::singleton = true; MirrorBallProjection MirrorBallProjection::singleton = true; class TransformInfo { public: double xRotate; double yRotate; double zRotate; int oversampleFactor; bool interpolate; Projection *srcProjection; Projection *dstProjection; TransformInfo() { xRotate = yRotate = zRotate = 0; oversampleFactor = 1; interpolate = false; srcProjection = dstProjection = NULL; } }; void transformArray( const pfs::Array2D *in, pfs::Array2D *out, TransformInfo *transformInfo); void printHelp() { fprintf( stderr, PROG_NAME " + [--width ] [--height ] [--oversample ] [--interpolate] [--xrotate ] [--yrotate ] [--zrotate ] [--verbose] [--help]\n" "See man page for more information.\n" ); } void panoramic( int argc, char* argv[] ) { pfs::DOMIO pfsio; // default parameters int xSize = -1; int ySize = -1; bool verbose = false; TransformInfo transformInfo; static struct option cmdLineOptions[] = { { "help", no_argument, NULL, 'e' }, { "verbose", no_argument, NULL, 'v' }, { "width", required_argument, NULL, 'w' }, { "height", required_argument, NULL, 'h' }, { "oversample", required_argument, NULL, 'o' }, { "interpolate", no_argument, NULL, 'i' }, { "xrotate", required_argument, NULL, 'x' }, { "yrotate", required_argument, NULL, 'y' }, { "zrotate", required_argument, NULL, 'z' }, { NULL, 0, NULL, 0 } }; int optionIndex = 0; while( true ) { int c = getopt_long( argc, argv, "w:h:o:ix:y:z:", cmdLineOptions, &optionIndex ); if( c == -1 ) break; switch( c ) { case 'e': printHelp(); throw QuietException(); case 'v': verbose = true; break; case 'w': xSize = strtol( optarg, NULL, 10 ); break; case 'h': ySize = strtol( optarg, NULL, 10 ); break; case 'o': transformInfo.oversampleFactor = strtol( optarg, NULL, 10 ); break; case 'i': transformInfo.interpolate = true; break; case 'x': transformInfo.xRotate = strtod( optarg, NULL ); break; case 'y': transformInfo.yRotate = strtod( optarg, NULL ); break; case 'z': transformInfo.zRotate = strtod( optarg, NULL ); break; case '?': throw QuietException(); case ':': throw QuietException(); } } if( optind < argc ) { bool set = false; while( optind < argc ) { char *destination; if( set || ( destination = strchr( argv[optind], '+' ) ) == NULL ) { fprintf( stderr, PROG_NAME " error: unknown parameter: %s\n", argv[optind]); throw QuietException(); } // replace 'plus' sign with a string terminator, // thus separating input and output filter names. *destination++ = '\0'; transformInfo.srcProjection = ProjectionFactory::getProjection( argv[optind] ); transformInfo.dstProjection = ProjectionFactory::getProjection( destination ); set = true; optind++; } } if( transformInfo.srcProjection == NULL || transformInfo.dstProjection == NULL ) { fprintf( stderr, PROG_NAME " error: unknown projection. Possible values:\n" ); ProjectionFactory::listProjectionNames(); throw QuietException(); } errorCheck(transformInfo.oversampleFactor > 0, "Oversample factor must be > 0"); if( verbose ) { fprintf( stderr, PROG_NAME ": reprojecting %s to %s", transformInfo.srcProjection->getName(), transformInfo.dstProjection->getName() ); if( transformInfo.interpolate == true ) fprintf( stderr, " with bilinear interpolation" ); if(transformInfo.oversampleFactor > 1) { if( transformInfo.interpolate == true ) fprintf( stderr, " and" ); else fprintf( stderr, " with" ); fprintf( stderr, " %dx%d oversampling", transformInfo.oversampleFactor, transformInfo.oversampleFactor ); } if( transformInfo.xRotate != 0 || transformInfo.yRotate != 0 || transformInfo.zRotate != 0 ) { fprintf( stderr, ", while rotating by %f, %f and %f degrees around X, Y and Z respectively", transformInfo.xRotate, transformInfo.yRotate, transformInfo.zRotate); } fprintf( stderr, ".\n"); } pfs::Frame *transformedFrame = NULL; bool firstFrame = true; while( true ) { pfs::Frame *frame = pfsio.readFrame( stdin ); if( frame == NULL ) break; // No more frames pfs::Channel *X, *Y, *Z; frame->getXYZChannels( X, Y, Z ); pfs::Channel *dX, *dY, *dZ; if(xSize == -1 && ySize == -1) { xSize = frame->getWidth(); ySize = (int)(xSize / transformInfo.dstProjection->getSizeRatio()); } else { if(xSize == -1) xSize = (int)(ySize * transformInfo.dstProjection->getSizeRatio()); if(ySize == -1) ySize = (int)(xSize / transformInfo.dstProjection->getSizeRatio()); } if( firstFrame ) { transformedFrame = pfsio.createFrame( xSize, ySize ); firstFrame = false; } pfs::ChannelIterator *it = frame->getChannels(); while( it->hasNext() ) { pfs::Channel *originalCh = it->getNext(); pfs::Channel *newCh = transformedFrame->createChannel( originalCh->getName() ); //TODO: major optimization: transform all channels in one go. transformArray( originalCh, newCh, &transformInfo ); } pfs::copyTags( frame, transformedFrame ); pfsio.writeFrame( transformedFrame, stdout ); pfsio.freeFrame( frame ); } pfsio.freeFrame( transformedFrame ); delete transformInfo.dstProjection; delete transformInfo.srcProjection; } void transformArray( const pfs::Array2D *in, pfs::Array2D *out, TransformInfo *transformInfo ) { const double delta = 1. / transformInfo->oversampleFactor; const double offset = 0.5 / transformInfo->oversampleFactor; const double scaler = 1. / ( transformInfo->oversampleFactor * transformInfo->oversampleFactor ); const int outRows = out->getRows(); const int outCols = out->getCols(); const int inRows = in->getRows(); const int inCols = in->getCols(); for( int y = 0; y < outRows; y++ ) for( int x = 0; x < outCols; x++ ) { double pixVal = 0; if( transformInfo->dstProjection->isValidPixel(( x + 0.5 ) / outCols, ( y + 0.5 ) / outCols ) == true ) { for( double sy = 0, oy = 0; oy < transformInfo->oversampleFactor; sy += delta, oy++ ) for( double sx = 0, ox = 0; ox < transformInfo->oversampleFactor; sx += delta, ox++ ) { Vector3D *direction = transformInfo->dstProjection->uvToDirection( ( x + offset + sx ) / outCols, ( y + offset + sy ) / outRows ); if(direction == NULL) continue; // angles below are negated, because we want to rotate // the environment around us, not us within the environment. if( transformInfo->xRotate != 0 ) direction->rotateX( -transformInfo->xRotate ); if( transformInfo->yRotate != 0 ) direction->rotateY( -transformInfo->yRotate ); if( transformInfo->zRotate != 0 ) direction->rotateZ( -transformInfo->zRotate ); Point2D *p = transformInfo->srcProjection->directionToUV( direction ); p->x *= inCols; p->y *= inRows; if( transformInfo->interpolate == true ) { int ix = (int)floor( p->x ); int iy = (int)floor( p->y ); double i = p->x - ix; double j = p->y - iy; // compute pixel weights for interpolation double w1 = i * j; double w2 = (1 - i) * j; double w3 = (1 - i) * (1 - j); double w4 = i * (1 - j); int dx = ix + 1; if(dx >= inCols) dx = inCols - 1; int dy = iy + 1; if(dy >= inRows) dy = inRows - 1; pixVal += w3 * (*in)(ix, iy) + w4 * (*in)(dx, iy) + w1 * (*in)(dx, dy) + w2 * (*in)(ix, dy); } else { int ix = (int)floor(p->x + 0.5); int iy = (int)floor(p->y + 0.5); if(ix >= inCols) ix = inCols - 1; if(iy >= inRows) iy = inRows - 1; pixVal += (*in)(ix, iy); } delete direction; delete p; (*out)(x,y) = (float)(pixVal * scaler); } } } } int main( int argc, char* argv[] ) { try { panoramic( argc, argv ); } catch( pfs::Exception ex ) { fprintf( stderr, PROG_NAME " error: %s\n", ex.getMessage() ); return EXIT_FAILURE; } catch( QuietException ex ) { return EXIT_FAILURE; } return EXIT_SUCCESS; } pfstools-2.2.0/src/filter/pfstag.cpp0000664000701400070140000001172014105165613016125 0ustar rkm38rkm38/** * @brief Add/Remove tags to frames in pfs stream * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Rafal Mantiuk and Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Rafal Mantiuk, * * $Id: pfstag.cpp,v 1.1 2005/06/15 13:36:54 rafm Exp $ */ #include #include #include #include #include #include #include #include using namespace std; #define PROG_NAME "pfstag" class QuietException { }; void printHelp() { fprintf( stderr, PROG_NAME " [--set \"[channel:]name=value\"] [--remove \"[channel:]name]\" [--verbose] [--help]\n" "See man page for more information.\n" ); } struct TagOperation { bool remove; string name, value; string channel; }; typedef list ListOfTags; TagOperation parseTagOperation( const char *tag, bool remove ) { TagOperation tagop; const char *equalSign = strstr( tag, "=" ); const char *colonSign = strstr( tag, ":" ); if( remove ) equalSign = tag + strlen( tag ); if( colonSign > equalSign || colonSign == tag ) colonSign = NULL; // ":" only before "=" if( !remove && (equalSign == NULL ) ) // = sign missing throw pfs::Exception( "Tag must contain '=' sign" ); if( (colonSign == NULL && equalSign == tag) || (colonSign != NULL && (equalSign-colonSign) <= 1 ) ) throw pfs::Exception( "Missing tag name" ); tagop.name = colonSign == NULL ? string( tag, (equalSign-tag) ) : string( colonSign+1, (equalSign-colonSign-1) ); if( !remove ) tagop.value = string( equalSign+1 ); // No channel -> frame tag if( colonSign != NULL ) tagop.channel = string( tag, (colonSign-tag) ); tagop.remove = remove; return tagop; } void setTagsOnFrames( int argc, char* argv[] ) { pfs::DOMIO pfsio; ListOfTags setTags; bool verbose = false; static struct option cmdLineOptions[] = { { "help", no_argument, NULL, 'h' }, { "verbose", no_argument, NULL, 'v' }, { "add", required_argument, NULL, 's' }, { "set", required_argument, NULL, 's' }, { "remove", required_argument, NULL, 'r' }, { NULL, 0, NULL, 0 } }; int optionIndex = 0; while( 1 ) { int c = getopt_long (argc, argv, "m:g:s:r:", cmdLineOptions, &optionIndex); if( c == -1 ) break; switch( c ) { case 'h': printHelp(); throw QuietException(); case 'v': verbose = true; break; case 's': setTags.push_back( parseTagOperation( optarg, false ) ); break; case 'r': setTags.push_back( parseTagOperation( optarg, true ) ); break; case '?': throw QuietException(); case ':': throw QuietException(); } } if( verbose ) { ListOfTags::iterator it; for( it = setTags.begin(); it != setTags.end(); it++ ) { TagOperation &tagop = *it; if( tagop.remove ) cerr << PROG_NAME ": remove tag '" << tagop.name << "'\n"; else cerr << PROG_NAME ": set tag '" << tagop.name << "' to '" << tagop.value << "'\n"; } } while( true ) { pfs::Frame *frame = pfsio.readFrame( stdin ); if( frame == NULL ) break; // No more frames ListOfTags::iterator it; for( it = setTags.begin(); it != setTags.end(); it++ ) { TagOperation &tagop = *it; pfs::TagContainer *tags; if( tagop.channel.empty() ) tags = frame->getTags(); else { pfs::Channel *channel = frame->getChannel( tagop.channel.c_str() ); if( channel == NULL ) throw pfs::Exception( "Channel not found" ); tags = channel->getTags(); } if( tagop.remove ) tags->removeTag( tagop.name.c_str() ); else tags->setString( tagop.name.c_str(), tagop.value.c_str() ); } pfsio.writeFrame( frame, stdout ); pfsio.freeFrame( frame ); } } int main( int argc, char* argv[] ) { try { setTagsOnFrames( argc, argv ); } catch( pfs::Exception ex ) { fprintf( stderr, PROG_NAME " error: %s\n", ex.getMessage() ); return EXIT_FAILURE; } catch( QuietException ex ) { return EXIT_FAILURE; } return EXIT_SUCCESS; } pfstools-2.2.0/src/filter/pfscolortransform.cpp0000664000701400070140000002007014105165613020422 0ustar rkm38rkm38/** * @brief Apply color transformation on the pfs stream * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Rafal Mantiuk and Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Delvin Varghese, * */ // TODO: Remove memory leaks (create matrix class) #include #include #include #include #include #include #include #include #include #define NUMELEMS(x) (sizeof(x)/sizeof(*(x))) #define PROG_NAME "pfscolortransform" /* EXAMPLE CONTENTS OF matrix_file.txt 0.111424,0.222579,0.333464 0.111656,0.222158,0.333186 0.111332,0.222193,0.333444 */ // static const float rgb2xyzD65Mat[3][3] = static const float rgb2xyzD65Mat[3][3] = { { 0.412424f, 0.357579f, 0.180464f }, { 0.212656f, 0.715158f, 0.072186f }, { 0.019332f, 0.119193f, 0.950444f } }; // static const float xyz2rgbD65Mat[3][3] = static const float xyz2rgbD65Mat[3][3] = { { 3.240708, -1.537259, -0.498570 }, { -0.969257, 1.875995, 0.041555 }, { 0.055636, -0.203996, 1.057069 } }; class QuietException {}; void printHelp() { std::cerr << PROG_NAME " [--xyzrgb matrix_file.txt | --rgbxyz matrix_file.txt] [--transpose]" << std::endl << "See man page for more information." << std::endl; } float** initialiseArray(const float referenceMat[][3]){ float** outputMat; outputMat = new float*[3]; for (int i = 0;i<3;i++){ outputMat[i] = new float[3]; } for (int i=0;i<3;i++){ for(int j=0;j<3;j++){ outputMat[i][j] = referenceMat[i][j]; } } return outputMat; } float** readMatrixFile(char* fn, bool transpose){ float** matArray; FILE *file = fopen(fn, "r"); if ( file ){ matArray = new float*[3]; for (int i = 0;i<3;i++){ matArray[i] = new float[3]; } size_t i, j, k; char buffer[BUFSIZ], *ptr, *end_ptr; for ( i = 0; fgets(buffer, sizeof buffer, file); ++i ){ for ( j = 0, ptr = buffer; j <= NUMELEMS(*matArray); ++j, ++ptr ){ if(!transpose){ matArray[i][j] = (float)strtof(ptr, &end_ptr); } else { matArray[j][i] = (float)strtof(ptr, &end_ptr); } if( end_ptr == ptr ) { // Parsing number issue std::ostringstream error_msg; error_msg << "Failed to parse line " << i+1 << " in '" << fn << "'"; throw pfs::Exception( error_msg.str().c_str() ); } ptr = end_ptr; } } fclose(file); } else{ // Parsing number issue std::ostringstream error_msg; error_msg << "Cannot open matrix file '" << fn << "'"; throw pfs::Exception( error_msg.str().c_str() ); } return matArray; } // float** multiplyMatrices(float matA[][3], float matB[][3]){ float** multiplyMatrices(float** matA, float** matB){ float** resultMat; resultMat = new float*[3]; for (int i = 0;i<3;i++){ resultMat[i] = new float[3]; } for (int i=0;i<3;i++){ for(int j=0;j<3;j++){ for(int k=0;k<3;k++){ resultMat[i][j] += matA[i][k] * matB[k][j]; } } } return resultMat; } // void applyTransform( pfs::Array2D *arrayX, pfs::Array2D *arrayY, pfs::Array2D *arrayZ, const float finalMatrix[][3]) void applyTransform( pfs::Array2D *arrayX, pfs::Array2D *arrayY, pfs::Array2D *arrayZ, float** finalMatrix) { int imgSize = arrayX->getRows()*arrayX->getCols(); for( int index = 0; index < imgSize ; index++ ) { float &x = (*arrayX)(index); // if( x < 0 ) x = 0; float &y = (*arrayY)(index); // if( y < 0 ) y = 0; float &z = (*arrayZ)(index); // if( z < 0 ) z = 0; x = finalMatrix[0][0]*x + finalMatrix[0][1]*x + finalMatrix[0][2]*x; y = finalMatrix[1][0]*y + finalMatrix[1][1]*y + finalMatrix[1][2]*y; z = finalMatrix[2][0]*z + finalMatrix[2][1]*z + finalMatrix[2][2]*z; // x = finalMatrix[0][0]*x + finalMatrix[0][1]*y + finalMatrix[0][2]*z; // y = finalMatrix[1][0]*x + finalMatrix[1][1]*y + finalMatrix[1][2]*z; // z = finalMatrix[2][0]*x + finalMatrix[2][1]*y + finalMatrix[2][2]*z; } } void printColorTransform( float **mat, bool opt_xyzrgb ) { fprintf( stderr, "Using color transformation:\n"); const char *left = "XYZ"; const char *right = "RGB"; if( opt_xyzrgb ) std::swap( left, right ); for(int i=0;i<3;i++){ if( i == 1 ) { fprintf( stderr, "[%s]' = [", left); } else fprintf( stderr, " ["); for(int j=0;j<3;j++){ if( j > 0 ) fprintf( stderr, " " ); fprintf( stderr, "%4f", mat[i][j]); } if( i == 1 ) { fprintf( stderr, "] * [%s]'\n", right ); } else fprintf( stderr, "]\n"); } } void applyTransformOnFrames( int argc, char *argv[] ){ pfs::DOMIO pfsio; bool verbose = false; bool transpose = false; bool opt_xyzrgb = false; bool opt_rgbxyz = false; // File name of Matrix file. char *matrix_file_name; static struct option cmdLineOptions[] = { { "help", no_argument, NULL, 'h' }, { "verbose", no_argument, NULL, 'v' }, { "transpose", no_argument, NULL, 't' }, { "xyzrgb", required_argument, NULL, 'x' }, { "rgbxyz", required_argument, NULL, 'r' }, { NULL, 0, NULL, 0 } }; int optionIndex = 0; while( 1 ) { int c = getopt_long (argc, argv, "r:x:htv", cmdLineOptions, &optionIndex); if( c == -1 ) break; switch( c ) { case 'h':{ printHelp(); throw QuietException(); } case 'v':{ verbose = true; break; } case 't':{ transpose = true; break; } case 'x':{ //char* matrixFn = optarg; matrix_file_name = optarg; opt_xyzrgb = true; break; } case 'r':{ matrix_file_name = optarg; opt_rgbxyz = true; break; } case '?': throw QuietException(); break; case ':': throw QuietException(); break; } } float** matrixFile = NULL; float** rec709Xuserval = NULL; float** referenceMat = NULL; matrixFile = readMatrixFile(matrix_file_name,transpose); if( verbose ) { printColorTransform( matrixFile, opt_xyzrgb ); } if(opt_rgbxyz){ referenceMat = initialiseArray(xyz2rgbD65Mat); rec709Xuserval = multiplyMatrices(matrixFile,referenceMat); } else if(opt_xyzrgb){ referenceMat = initialiseArray(rgb2xyzD65Mat); rec709Xuserval = multiplyMatrices(referenceMat,matrixFile); } while( true ){ pfs::Frame *frame = pfsio.readFrame( stdin ); if( frame == NULL ) break; // No more frames pfs::Channel *X, *Y, *Z; frame->getXYZChannels( X, Y, Z ); // TODO : Processing here if( X != NULL ) { // Color, XYZ applyTransform( X, Y, Z, rec709Xuserval); } else throw pfs::Exception( "Missing X, Y, Z channels in the PFS stream" ); pfsio.writeFrame( frame, stdout ); pfsio.freeFrame( frame ); } } int main( int argc, char* argv[] ) { try { applyTransformOnFrames( argc, argv ); } catch( pfs::Exception ex ) { std::cerr << PROG_NAME << " error: " << ex.getMessage() << std::endl; return EXIT_FAILURE; } catch( QuietException ex ) { return EXIT_FAILURE; } return EXIT_SUCCESS; }pfstools-2.2.0/src/filter/pfspad.10000664000701400070140000000355114105165613015477 0ustar rkm38rkm38.TH "pfspad" 1 .SH NAME pfspad \- Add borders to a frame in PFS stream .SH SYNOPSIS .B pfspad [--left ] [--right ] [--top ] [--bottom ] [--width ] [--height ] [--R ] [--G ] [--B ] [--Y ] [--help] .SH DESCRIPTION Add borders to each frame in PFS stream. User specifies size of left, right, top and bottom border or size of an output image. Color of borders can be specified as well. .SH OPTIONS .TP --left , -l Width in pixels of the left border. .TP --right , -r Width in pixels of the right border. .TP --top , -t Height in pixels of the top border. .TP --bottom , -b Height in pixels of the bottom border. .TP --width , -W Width of an output image. Note that --width can be mixed with either --left or --right option. .TP --height , -H Height of an output image. Note that --height can be mixed with either --top or --bottom option. .TP --R , -R , --G , -G , --B , -B Color of borders in RGB mode. Default color is black, and if some of the components is not specified, its value is set to 0.0. .TP --Y , -Y Color of borders in luminance mode. .TP --help, -h Print a list of commandline options. .SH EXAMPLES .TP pfsin image.hdr | pfspad --left 50 --bottom 20 | pfsout out.hdr Add 50 columns to the left and 20 rows to the bottom side of image.hdr. Color of borders is default black. .TP pfsin image.hdr | pfspad --top 20 --height 500 --width 400 --R 1.0 | pfsout out.hdr Add 20 rows to the top side of image.hdr and create output image out.hdr 500 pixels in height and 400 pixels in width. Color of borders is set to red. .SH SEE ALSO .BR pfsin (1) .BR pfsout (1) .SH BUGS Please report bugs and comments to Dorota Zdrojewska . pfstools-2.2.0/src/filter/pfsrotate.10000664000701400070140000000160414105165613016226 0ustar rkm38rkm38.TH "pfsrotate" 1 .SH NAME pfsrotate \- Rotate images 90 degrees. .SH SYNOPSIS .B pfsrotate [--r] [--help] .SH DESCRIPTION Use this command to rotate images from a stream of pfs. This command is useful for output on hardcopy devices with aspect ratios opposite to the input image. By default, the image is rotated clockwise. The --r option may be used to rotate the image counter-clockwise instead. .SH OPTIONS .TP --r, -r Rotate the image counter-clockwise instead of clockwise. .SH EXAMPLES .TP pfsin memorial.hdr | pfsrotate -r | pfsout memorial_gc.hdr Rotate memorial image 90 degrees counter-clockwise and save to memorial_gc.hdr. .SH "NOTES" To rotate an image 180 degrees, use .BR pfsflip (1) with both the --h and --v options specified. .SH "SEE ALSO" .BR pfsin(1) .BR pfsout(1) .BR pfsflip(1) .SH BUGS Please report bugs and comments to Alexander Efremov . pfstools-2.2.0/src/filter/pfscut.cpp0000664000701400070140000001671414105165613016155 0ustar rkm38rkm38/** * @brief Cut a rectangle out of images in PFS stream * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Rafal Mantiuk and Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Dorota Zdrojewska, * */ #include #include #include #include #include #define PROG_NAME "pfscut" #define UNSP INT_MAX #define MIN 0 #define MAX 1 #define SIZE 2 using namespace std; class QuietException { }; static struct option cmdLineOptions[] = { {"help", no_argument, NULL, 'h'}, {"left", required_argument, NULL, 'l'}, {"right", required_argument, NULL, 'r'}, {"top", required_argument, NULL, 't'}, {"bottom", required_argument, NULL, 'b'}, {"width", required_argument, NULL, 'W'}, {"height", required_argument, NULL, 'H'}, {NULL, 0, NULL, 0} }; void printHelp() { fprintf( stderr, PROG_NAME ": \n" "\t[--left ]\n" "\t[--right ]\n" "\t[--top ]\n" "\t[--bottom ]\n" "\t[--width ]\n" "\t[--height ]\n" "\t[--help]\n" "\t[x_ul y_ul x_br y_br]\n" "See man page for more information.\n"); } void calcBorders(int min, int max, int size, int inSize, int flag, int* out) { if (min<0 || max<0) throw pfs::Exception("You cannot cut negative number of pixels"); if (min>=inSize && min!=UNSP) throw pfs::Exception("You cannot cut number of pixels greater than size of an image"); if (max>=inSize && max!=UNSP) throw pfs::Exception("You cannot cut number of pixels greater than size of an image"); if (size>inSize && size!=UNSP) throw pfs::Exception("You cannot specify size greater than size of an input image"); if (min!=UNSP && max!=UNSP && size!=UNSP) { if (flag) throw pfs::Exception("You cannot specify all width, left and right options"); else throw pfs::Exception("You cannot specify all height, top and bottom options"); } if (min!=UNSP && max!=UNSP && size==UNSP) { if((min+max)>=inSize) { if (flag) throw pfs::Exception("Too large value for left or right option"); else throw pfs::Exception("Too large value for top or bottom option"); } out[MIN]=min; out[MAX]=inSize-max-1; out[SIZE]=inSize-min-max; } if (min!=UNSP && max==UNSP && size==UNSP) { out[MIN]=min; out[MAX]=inSize-1; out[SIZE]=inSize-min; } if (min==UNSP && max!=UNSP && size==UNSP) { out[MIN]=0; out[MAX]=inSize-max-1; out[SIZE]=inSize-max; } if (min!=UNSP && max==UNSP && size!=UNSP) { if((min+max)>inSize) { if(flag) throw pfs::Exception("Too large value for left or width option"); else throw pfs::Exception("Too large value for top or height option"); } out[MIN]=min; out[MAX]=size+min-1; out[SIZE]=size; } if (min==UNSP && max!=UNSP && size!=UNSP) { if((max+size)>inSize) { if(flag) throw pfs::Exception("Too large value for right or width option"); else throw pfs::Exception("Too large value for bottom or height option"); } out[MIN]=inSize-max-size; out[MAX]=inSize-max-1; out[SIZE]=size; } if (min==UNSP && max==UNSP && size!=UNSP) { int diff=inSize-size; out[MIN]=diff/2; out[MAX]=inSize-(diff/2)-1; if(diff%2) out[MAX]--; out[SIZE]=size; } if (min==UNSP && max==UNSP && size==UNSP) { out[MIN]=0; out[MAX]=inSize-1; out[SIZE]=inSize; } } void pfscut (int argc, char* argv[]) { pfs::DOMIO pfsio; //numbers of pixels to cut from each border of an image int left=UNSP, right=UNSP, top=UNSP, bottom=UNSP; int width=UNSP, height=UNSP; //size of an output image int x_ul=UNSP, y_ul=UNSP, x_br=UNSP, y_br=UNSP; int optionIndex=0; while (1) { int c=getopt_long(argc, argv, "hl:r:t:b:W:H:", cmdLineOptions, &optionIndex); if(c==-1) break; switch(c) { case 'h': printHelp(); throw QuietException(); break; case 'l': left=atoi(optarg); break; case 'r': right=atoi(optarg); break; case 't': top=atoi(optarg); break; case 'b': bottom=atoi(optarg); break; case 'W': width=atoi(optarg); if (width<=0) throw pfs::Exception("Size of an output image cannot be negative or equal to 0"); break; case 'H': height=atoi(optarg); if (height<=0) throw pfs::Exception("Size of an output image cannot be negative or equal to 0"); break; } } if( optind != argc ) { if( optind != (argc - 4) ) throw pfs::Exception( "You must specify x and y coordinates of both upper left and lower right corner" ); x_ul = strtol( argv[optind++], NULL, 10 ); y_ul = strtol( argv[optind++], NULL, 10 ); x_br = strtol( argv[optind++], NULL, 10 ); y_br = strtol( argv[optind++], NULL, 10 ); } while (1) { pfs::Frame *inFrame = pfsio.readFrame(stdin); if (inFrame==NULL) break; // no more frames int inWidth=inFrame->getWidth(); int inHeight=inFrame->getHeight(); int leftRight[3], topBottom[3]; if( x_ul != UNSP ) { leftRight[MIN] = x_ul; leftRight[MAX] = x_br; leftRight[SIZE] = x_br - x_ul + 1; topBottom[MIN] = y_ul; topBottom[MAX] = y_br; topBottom[SIZE] = y_br - y_ul + 1; } else { //calculate edge columns and rows of an input image to be in an output image calcBorders(left, right, width, inWidth, 1, leftRight); calcBorders(top, bottom, height, inHeight, 0, topBottom); } if( topBottom[SIZE] < 1 || leftRight[SIZE] < 1 ) throw pfs::Exception( "The resulting image has zero or negative size" ); if( topBottom[MIN] < 0 || leftRight[MIN] < 0 || topBottom[MAX] >= inHeight || leftRight[MAX] >= inWidth ) throw pfs::Exception( "The specified coordinates are outsize the image boundaries" ); int lCol=leftRight[MIN]; int rCol=leftRight[MAX]; int tRow=topBottom[MIN]; int bRow=topBottom[MAX]; int outWidth=leftRight[SIZE]; int outHeight=topBottom[SIZE]; pfs::Frame *outFrame = pfsio.createFrame(outWidth, outHeight); pfs::ChannelIterator *it = inFrame->getChannels(); while (it->hasNext()) { pfs::Channel *inCh = it->getNext(); pfs::Channel *outCh = outFrame->createChannel(inCh->getName()); for (int i=tRow; i<=bRow; i++) for (int j=lCol; j<=rCol; j++) (*outCh)(j-lCol, i-tRow)=(*inCh)(j,i); } pfs::copyTags(inFrame, outFrame); pfsio.writeFrame(outFrame, stdout); pfsio.freeFrame(inFrame); pfsio.freeFrame(outFrame); } } int main (int argc, char* argv[]) { try { pfscut(argc, argv); } catch (pfs::Exception ex) { fprintf (stderr, PROG_NAME " error: %s\n", ex.getMessage()); return EXIT_FAILURE; } catch (QuietException ex) { return EXIT_FAILURE; } return EXIT_SUCCESS; } pfstools-2.2.0/src/filter/pfsdisplayfunction.cpp0000664000701400070140000001305114105165613020564 0ustar rkm38rkm38/** * @brief Apply display function or inverse display function the the pfs stream * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Rafal Mantiuk and Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Rafal Mantiuk, * * $Id: pfsdisplayfunction.cpp,v 1.2 2008/07/29 16:14:29 rafm Exp $ */ #include #include #include #include #include #include #include #include "display_function.h" #define PROG_NAME "pfsdisplayfunction" class QuietException { }; void applyDisplayFunction( pfs::Array2D *array, DisplayFunction *df, int model_dir ); void printHelp() { fprintf( stderr, PROG_NAME " [--gamma | --inverse-gamma ] [--mul ] [--verbose] [--help]\n" "See man page for more information.\n" ); } void processFrames( int argc, char* argv[] ) { pfs::DOMIO pfsio; DisplayFunction *df = NULL; bool verbose = false; int model_dir = 0; // -1 to-pixels; 1 to-luminance; 0 unspecified df = createDisplayFunctionFromArgs( argc, argv ); if( df == NULL ) df = new DisplayFunctionGGBA( "lcd" ); static struct option cmdLineOptions[] = { { "help", no_argument, NULL, 'h' }, { "verbose", no_argument, NULL, 'v' }, { "to-pixels", no_argument, NULL, 'p' }, { "to-luminance", no_argument, NULL, 'l' }, { NULL, 0, NULL, 0 } }; int optionIndex = 0; while( 1 ) { int c = getopt_long (argc, argv, "hvpl", cmdLineOptions, &optionIndex); if( c == -1 ) break; switch( c ) { case 'h': printHelp(); throw QuietException(); case 'v': verbose = true; break; case 'l': model_dir = 1; break; case 'p': model_dir = -1; break; case '?': throw QuietException(); case ':': throw QuietException(); } } if( verbose ) { df->print( stderr ); } bool first_frame = true; while( true ) { pfs::Frame *frame = pfsio.readFrame( stdin ); if( frame == NULL ) break; // No more frames if( first_frame ) { const char *lum_type = frame->getTags()->getString("LUMINANCE"); if( lum_type ) { if( model_dir == 0 ) { // Determine model direction from the LUMINANCE tag if( !strcmp( lum_type, "DISPLAY" ) ) model_dir = 1; else if( !strcmp( lum_type, "ABSOLUTE" ) ) model_dir = -1; } if( !strcmp( lum_type, "DISPLAY" ) && model_dir == -1 ) std::cerr << PROG_NAME " warning: applying inverse display function to a display referred image" << std::endl; if( !strcmp( lum_type, "ABSOLUTE" ) && model_dir == 1 ) std::cerr << PROG_NAME " warning: applying display function to a linear luminance or radiance image" << std::endl; if( !strcmp( lum_type, "RELATIVE" ) ) std::cerr << PROG_NAME " warning: input image should be in absolute luminance / radiance units" << std::endl; } } if( model_dir == 0 ) throw pfs::Exception( "specify --to-pixels or --to-luminance mapping" ); pfs::Channel *X, *Y, *Z; frame->getXYZChannels( X, Y, Z ); if( X != NULL ) { // Color, XYZ pfs::transformColorSpace( pfs::CS_XYZ, X, Y, Z, pfs::CS_RGB, X, Y, Z ); // At this point (X,Y,Z) = (R,G,B) applyDisplayFunction( X, df, model_dir ); applyDisplayFunction( Y, df, model_dir ); applyDisplayFunction( Z, df, model_dir ); pfs::transformColorSpace( pfs::CS_RGB, X, Y, Z, pfs::CS_XYZ, X, Y, Z ); // At this point (X,Y,Z) = (X,Y,Z) } else if( (Y = frame->getChannel( "Y" )) != NULL ) { // Luminance only applyDisplayFunction( Y, df, model_dir ); } else throw pfs::Exception( "Missing X, Y, Z channels in the PFS stream" ); if( model_dir == -1 ) frame->getTags()->setString("LUMINANCE", "DISPLAY"); else if( model_dir == 1 ) frame->getTags()->setString("LUMINANCE", "ABSOLUTE"); first_frame = false; pfsio.writeFrame( frame, stdout ); pfsio.freeFrame( frame ); } } void applyDisplayFunction( pfs::Array2D *array, DisplayFunction *df, int model_dir ) { int imgSize = array->getRows()*array->getCols(); for( int index = 0; index < imgSize ; index++ ) { float &v = (*array)(index); if( model_dir == -1 ) v = df->inv_display( v ); else v = df->display( v ); } } int main( int argc, char* argv[] ) { try { processFrames( argc, argv ); } catch( pfs::Exception ex ) { fprintf( stderr, PROG_NAME " error: %s\n", ex.getMessage() ); return EXIT_FAILURE; } catch( QuietException ex ) { return EXIT_FAILURE; } return EXIT_SUCCESS; } pfstools-2.2.0/src/filter/pfsretime.cpp0000664000701400070140000001055514105165613016644 0ustar rkm38rkm38/** * @brief Retime animation sequence * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Rafal Mantiuk and Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Rafal Mantiuk, * * $Id: pfsretime.cpp,v 1.1 2013/01/29 22:15:01 rafm Exp $ */ #include #include #include #include #include #include #include #include #define PROG_NAME "pfsretime" class QuietException { }; void applyGamma( pfs::Array2D *array, const float exponent, const float multiplier ); void printHelp() { fprintf( stderr, PROG_NAME " [--in-fps ] [--out-fps ] [--speedup ] [--verbose] [--help]\n" "See man page for more information.\n" ); } void retimeFrames( int argc, char* argv[] ) { pfs::DOMIO pfsio; float in_fps = 30.f; float out_fps = 30.f; float speedup = -1.f; bool verbose = false; static struct option cmdLineOptions[] = { { "help", no_argument, NULL, 'h' }, { "verbose", no_argument, NULL, 'v' }, { "in-fps", required_argument, NULL, 'i' }, { "out-fps", required_argument, NULL, 'o' }, { "speedup", required_argument, NULL, 's' }, { NULL, 0, NULL, 0 } }; int optionIndex = 0; while( 1 ) { int c = getopt_long (argc, argv, "i:o:s:hv", cmdLineOptions, &optionIndex); if( c == -1 ) break; switch( c ) { case 'h': printHelp(); throw QuietException(); case 'v': verbose = true; break; case 's': speedup = (float)strtod( optarg, NULL ); break; case 'i': in_fps = (float)strtod( optarg, NULL ); break; case 'o': out_fps = (float)strtod( optarg, NULL ); break; case '?': throw QuietException(); case ':': throw QuietException(); } } std::ostringstream fps_tag; bool first_frame = true; float in_pos = 0; bool get_new_frame = true; pfs::Frame *frame = NULL; while( true ) { if( in_pos >= 1 ) { get_new_frame = true; in_pos -= 1; } if( get_new_frame ) { frame = pfsio.readFrame( stdin ); if( frame == NULL ) break; // No more frames if( first_frame ) { const char *fps_str = frame->getTags()->getString("FPS"); if( fps_str != NULL ) { in_fps = (float)strtod( fps_str, NULL ); } if( speedup != -1.f ) { out_fps = in_fps; in_fps /= speedup; } VERBOSE_STR << "in-fps: " << in_fps << " out-fps: " << out_fps<< std::endl; fps_tag << out_fps; first_frame = false; } frame->getTags()->setString("FPS", fps_tag.str().c_str() ); get_new_frame = false; } if( in_pos >= 1 ) // skip input frames continue; pfsio.writeFrame( frame, stdout ); in_pos += in_fps / out_fps; } if( frame != NULL ) pfsio.freeFrame( frame ); } int main( int argc, char* argv[] ) { try { retimeFrames( argc, argv ); } catch( pfs::Exception ex ) { fprintf( stderr, PROG_NAME " error: %s\n", ex.getMessage() ); return EXIT_FAILURE; } catch( QuietException ex ) { return EXIT_FAILURE; } return EXIT_SUCCESS; } pfstools-2.2.0/src/filter/pfsretime.10000664000701400070140000000321214105165613016212 0ustar rkm38rkm38.TH "pfsretime" 1 .SH NAME pfsretime \- Retime an animation stream from one frame-rate to another .SH SYNOPSIS .B pfsretime [--\fBin-fps\fR | --\fBout-fps\fR ] [--\fBspeedup\fR ] .SH DESCRIPTION Changes the frame-rate of the animation stream from the input-frame rate to the output frame-rate. Currently this is done by skipping or replicating frames. The command can be useful for creating time-lapse animations with temporal tone-mapping operators. .SH OPTIONS .TP --\fBin-fps\fR , -\fBi\fR The frame-rate of the input animation stream in frames per second. Fractional numbers are supported. By default, the FPS tag in the stream is used. If the tag cannot be found, 30 frames per second is assumed. .TP --\fBout-fps\fR , -\fBo\fR The frame-rate of the output animation stream in frames per second. Fractional numbers are supported. The default value is 30 frames per second. .TP --\fBspeedup\fR , -\fBs\fR How much faster (factor > 1) or slower (factor < 1) the output animation should run as compared with the input animation. The output frame-rate is kept the same as the input frame-rate. .SH EXAMPLES .TP pfsin frame%04d.hdr | pfsretime -v -i 1 -o 30 | pfstmo_mantiuk08 | pfsout res/frame%04d.jpg .IP Read the sequence of animation frames at 1 frame per second and output the sequence at 30 frames per second. This will replicate each input frame 30 times. The frames are then tone-mapped and stored in the res folder. .SH "SEE ALSO" .BR pfsin (1) .BR pfsout (1) .BR pfstmo_mantiuk08 (1) .SH BUGS Please report bugs and comments to the pfstools discussion group (http://groups.google.com/group/pfstools). pfstools-2.2.0/src/filter/pfssize.10000664000701400070140000000375014105165613015706 0ustar rkm38rkm38.TH "pfssize" 1 .SH NAME pfssize \- Resize frames .SH SYNOPSIS .B pfssize [--x ] [--y ] [--ratio ] [--maxx ] [--maxy ] [--minx ] [--miny ] [--filter ] .SH DESCRIPTION Resize all frames and all channels in the stream. Note that resampling is done on each channel as it is - for color images resampling is usually done in linear (not gamma corrected) XYZ color space. .SH OPTIONS .TP --x , -x New x resolution in pixels. .TP --y , -y New y resolution in pixels. .TP --ratio , -r Resize both width and height using the given ratio. Ratio equal 1 is the original size, lower than 1 makes the frames smaller and greater than 1, enlarges the frames. .TP --minx , --miny Make sure that the resulting image has at least width and/or height. Cannot be used in combination with -x, -y and --ratio. .TP --maxx , --maxy Make sure that the resulting image has at most width and/or height. Cannot be used in combination with -x, -y and --ratio. .TP --filter , -f Use filter for upsampling (on downsampling, box filter is always used). Available filters: .B BOX Box filter. This is the fastest filter, but it also causes visible aliasing artifacts. .B LINEAR (default) Bi-linear filter. .B MITCHELL Mitchell filter. From \fIMitchell and Netravali, Reconstruction Filters in Computer Graphics,In Computer Graphics, vol. 22 (4) 1988\fR. Since the filter contains negative parts, it may cause halo artifacts and it may result in negative values for HDR images. .SH EXAMPLES .TP pfsin memorial.hdr | pfssize -r 0.25 | pfsout memorial_small.hdr Lower the resolution of memorial.hdr four times. .SH "SEE ALSO" .BR pfsin (1) .BR pfsout (1) .SH BUGS Please report bugs and comments to Rafal Mantiuk . pfstools-2.2.0/src/filter/display_function.h0000664000701400070140000000503414105165613017661 0ustar rkm38rkm38/** * @brief Display Adaptive TMO * * From: * Rafal Mantiuk, Scott Daly, Louis Kerofsky. * Display Adaptive Tone Mapping. * To appear in: ACM Transactions on Graphics (Proc. of SIGGRAPH'08) 27 (3) * http://www.mpi-inf.mpg.de/resources/hdr/datmo/ * * This file is a part of PFSTMO package. * ---------------------------------------------------------------------- * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Rafal Mantiuk, * * $Id: display_function.h,v 1.1 2008/06/18 22:42:40 rafm Exp $ */ #ifndef DISPLAY_FUNCTION_H #define DISPLAY_FUNCTION_H #include class DisplayFunction { public: /** Convert input luminance (cd/m^2) to pixel value (0-1) */ virtual float inv_display( float L ) = 0; /** Convert pixel value (0-1) to input luminance (cd/m^2) */ virtual float display( float pix ) = 0; virtual void print( FILE *fh ) = 0; virtual ~DisplayFunction() { } }; /** * Gamma Gain Black and Ambient display model */ class DisplayFunctionGGBA : public DisplayFunction { float gamma, L_max, L_offset, L_black, E_amb, screen_refl; public: DisplayFunctionGGBA( float gamma, float L_max, float L_black, float E_amb, float screen_refl ); DisplayFunctionGGBA( const char *predefined ); float inv_display( float L ); float display( float pix ); void print( FILE *fh ); private: void init( float gamma, float L_max, float L_black, float E_amb, float screen_refl ); }; class DisplayFunctionLUT : public DisplayFunction { float *pix_lut, *L_lut; size_t lut_size; public: DisplayFunctionLUT( const char *file_name ); ~DisplayFunctionLUT(); float inv_display( float L ); float display( float pix ); void print( FILE *fh ); }; DisplayFunction *createDisplayFunctionFromArgs( int &argc, char* argv[] ); #endif pfstools-2.2.0/src/pfs/0002775000701400070140000000000014105165613013441 5ustar rkm38rkm38pfstools-2.2.0/src/pfs/pfsutils.cpp0000664000701400070140000002532014105165613016016 0ustar rkm38rkm38/** * @brief PFS library - additional utilities * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Rafal Mantiuk and Grzegorz Krawczyk * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Rafal Mantiuk, * * $Id: pfsutils.cpp,v 1.4 2006/09/21 21:42:54 rafm Exp $ */ #include #include #include #include #include #include #include "pfs.h" #define MAX_FRAME 99999 namespace pfs { using namespace std; string getColorspaceString(ColorSpace cs){ switch(cs){ case CS_XYZ: return "XYZ"; case CS_RGB: return "RGB"; case CS_SRGB: return "sRGB"; case CS_PQYCbCr2020: return "PQ YCbCr 2020"; case CS_YCbCr709: return "YCbCr BT.709"; case CS_HLGYCbCr2020: return "HLG YCbCr 2020"; case CS_RGB2020: return "RGB2020"; default: return "Unknown"; } } string intToString(int a){ stringstream ss; ss << a; return ss.str(); } static void removeCommandLineArg( int &argc, char* argv[], int firstArgToRemove, int numArgsToRemove = 1 ); static void parseFrameRange( const char *rangeString, int &firstFrame, int &lastFrame, int &everyNthFrame ); struct FilePattern { const char *pattern; int firstFrame; int lastFrame; bool skipMissingFrames; int everyNthFrame; int currentFrame; bool isPattern; FILE *stdinout; // Use this fh if "-" found istead of file name FilePattern( const char *pattern, FILE *n_stdinout ): pattern( pattern ), firstFrame( 0 ), lastFrame( MAX_FRAME ), skipMissingFrames( false ), currentFrame( 0 ), everyNthFrame( 1 ) { isPattern = strstr( pattern, "%" ) != NULL; if( !strcmp( pattern, "-" ) ) stdinout = n_stdinout; else stdinout = NULL; } }; class FrameFileIteratorImpl { protected: char *pattern; const char *fopenMode; char fileName[1024]; FILE *stdinout; typedef list PatternList; PatternList patternList; PatternList::iterator currentPattern; public: FrameFileIteratorImpl( int &argc, char* argv[], const char *fopenMode, const char *fileNamePrefix, FILE *stdinout, const char *optstring, const struct option *getopt_long ) : fopenMode( fopenMode ), stdinout( stdinout ) { PatternList::pointer lastPattern = NULL; for( int i=1 ; i= argc ) throw CommandLineException( "Missing frame range after '--frame' switch" ); if( lastPattern == NULL ) throw CommandLineException( "File pattern must be specified before '--frame' switch" ); parseFrameRange( argv[i+1], lastPattern->firstFrame, lastPattern->lastFrame, lastPattern->everyNthFrame ); lastPattern->currentFrame = lastPattern->firstFrame; removeCommandLineArg( argc, argv, i, 2 ); } else if( fileNamePrefix != NULL && !strcmp( argv[i], fileNamePrefix ) ) { if( i+1 >= argc ) throw CommandLineException( "Missing file name" ); patternList.push_back( FilePattern( argv[i+1], stdinout ) ); lastPattern = &patternList.back(); removeCommandLineArg( argc, argv, i, 2 ); } else if( !strcmp( argv[i], "--skip-missing" ) ) { if( lastPattern == NULL ) throw CommandLineException( "File pattern must be specified before '--skip-missing' switch" ); lastPattern->skipMissingFrames = true; removeCommandLineArg( argc, argv, i, 1 ); } else if( fileNamePrefix == NULL && ( argv[i][0] != '-' || !strcmp( argv[i], "-" )) ) { patternList.push_back( FilePattern( argv[i], stdinout ) ); lastPattern = &patternList.back(); removeCommandLineArg( argc, argv, i, 1 ); } else { bool optionProcessed = false; if( getopt_long != NULL && !strncmp( argv[i], "--", 2 ) ) { // Make sure that option parameters are processed properly const struct option *opt = getopt_long; while( opt->name != NULL ) { if( !strncmp( argv[i]+2, opt->name, strlen(opt->name) ) ) { if( opt->has_arg == required_argument ) { if( argv[i][strlen(opt->name)+2] == '=' ) { // --long=arg i++; optionProcessed = true; break; } else { // --long arg i += 2; optionProcessed = true; break; } } else if( opt->has_arg == no_argument ) { i += 1; optionProcessed = true; break; } else throw CommandLineException( "Internal error: FrameFileIterator can handle only required_argument and no_argument options" ); } opt++; } } if( !optionProcessed && optstring != NULL && argv[i][0] == '-' ) { for( const char *opt = optstring; *opt != 0; opt++ ) { if( argv[i][1] == *opt ) { if( *(opt+1) == ':' ) { i += 2; } else { i += 1; } optionProcessed = true; break; } } } if( !optionProcessed ) i++; } } currentPattern = patternList.begin(); } FrameFile getNextFrameFile( ) { while( currentPattern != patternList.end() ) { int skippedFrames = 0; do { if( (currentPattern->currentFrame > currentPattern->lastFrame && currentPattern->everyNthFrame > 0) || (currentPattern->currentFrame < currentPattern->lastFrame && currentPattern->everyNthFrame < 0) ) break; if( currentPattern->isPattern ) sprintf( fileName, currentPattern->pattern, currentPattern->currentFrame ); else { // Single file, not a pattern strcpy( fileName, currentPattern->pattern ); if( currentPattern->stdinout == NULL ) { //Force to step to next pattern in the list, but not for stdin/out currentPattern->currentFrame = currentPattern->lastFrame; } } FILE *fh; if( currentPattern->stdinout != NULL ) fh = currentPattern->stdinout; else fh = fopen( fileName, fopenMode ); currentPattern->currentFrame += currentPattern->everyNthFrame; if( fh != NULL ) return FrameFile( fh, fileName ); if( !currentPattern->isPattern || (currentPattern->currentFrame-currentPattern->everyNthFrame) == currentPattern->firstFrame ) { std::ostringstream msg; msg << "Can not open file '" << fileName << "'"; // char msg[1024]; // sprintf( msg, "Can not open file '%s'", fileName ); throw pfs::Exception( msg.str().c_str() ); } skippedFrames++; } while( currentPattern->skipMissingFrames && skippedFrames < 10 ); currentPattern++; } return FrameFile( NULL, NULL ); } void closeFrameFile( FrameFile &frameFile ) { if( frameFile.fh != NULL && frameFile.fh != stdinout ) fclose( frameFile.fh ); frameFile.fh = NULL; } }; FrameFileIterator::FrameFileIterator( int &argc, char* argv[], const char *fopenMode, const char *fileNamePrefix, FILE *stdinout, const char *optstring, const struct option *getopt_long ) { impl = new FrameFileIteratorImpl( argc, argv, fopenMode, fileNamePrefix, stdinout, optstring, getopt_long ); } FrameFileIterator::~FrameFileIterator() { delete impl; } FrameFile FrameFileIterator::getNextFrameFile( ) { return impl->getNextFrameFile(); } void FrameFileIterator::closeFrameFile( FrameFile &frameFile ) { return impl->closeFrameFile( frameFile ); } void FrameFileIterator::printUsage( FILE *out, const char *progName ) { fprintf( out, "Usage: %s [switches] [--frames ] [--skip-missing] []...\n\n" " can contain '%%d' to process a sequence of frames. To insert leading zeros use '%%0Nd', where n is a number of zeros. Any number of s can be given in a command line. They are processed one after another. Switches --frames and --skip-missing always refer to the last \n" "\nSwitches:\n" " --frames : range of frame numbers to process. Range is given Octave range format, e.g. 10:2:100, to process every second frame, starting from 10 and stopping at 100\n" " --skip-missing : skip up to 10 consequtive frames if there are missing\n", progName ); } static void parseFrameRange( const char *rangeString, int &firstFrame, int &lastFrame, int &everyNthFrame ) { firstFrame = 0; lastFrame = MAX_FRAME; everyNthFrame = 1; char *nextToken; int l1 = strtol( rangeString, &nextToken, 10 ); if( nextToken != rangeString ) firstFrame = l1; if( nextToken[0] != ':' ) throw CommandLineException( "Missing ':' in the frame range specification" ); nextToken++; char *currentToken = nextToken; int l2 = strtol( currentToken, &nextToken, 10 ); if( currentToken != nextToken ) lastFrame = l2; if( nextToken[0] == ':' ) { everyNthFrame = lastFrame; lastFrame = MAX_FRAME; nextToken++; currentToken = nextToken; int l3 = strtol( currentToken, &nextToken, 10 ); if( currentToken != nextToken ) lastFrame = l3; } } static void removeCommandLineArg( int &argc, char* argv[], int firstArgToRemove, int numArgsToRemove ) { assert( firstArgToRemove+numArgsToRemove <= argc ); if( argc-firstArgToRemove-numArgsToRemove > 0 ) { for( int i = firstArgToRemove; i < argc-numArgsToRemove; i++ ) argv[i] = argv[i+numArgsToRemove]; } argc -= numArgsToRemove; } } pfstools-2.2.0/src/pfs/CMakeLists.txt0000664000701400070140000000307214105165613016201 0ustar rkm38rkm38 include_directories ("${PROJECT_BINARY_DIR}/") if( NOT HAS_GETOPT ) include_directories ("${GETOPT_INCLUDE}") endif( NOT HAS_GETOPT ) add_library(pfs ${LIB_TYPE} colorspace.cpp pfs.cpp pfsutils.cpp array2d.h pfs.h "${GETOPT_OBJECT}") # SOVERSION changes only when the library API changes, so it may be # different from the package version set_target_properties(pfs PROPERTIES VERSION 2.0.0 SOVERSION 2) # TODO: Make it platform dependent - only GCC linux / perhaps Mac # This is needed when linking with matlab mex files SET( CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fPIC" ) SET( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC" ) # Replace a few tags in pfs.pc.in file, create pfs.pc file(READ ${CMAKE_CURRENT_SOURCE_DIR}/pfs.pc.in file_content) #message("Input:\n${file_content}") set (file_content_res) string(REGEX REPLACE "(@prefix@)" "${CMAKE_INSTALL_PREFIX}" file_content_res "${file_content}") string(REGEX REPLACE "(@PACKAGE_VERSION@)" "${pfstools_VERSION_MAJOR}.${pfstools_VERSION_MINOR}" file_content_res "${file_content_res}") #message("Output:\n${file_content_res}") file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/pfs.pc" "${file_content_res}") install (TARGETS pfs LIBRARY DESTINATION lib${LIB_SUFFIX} ARCHIVE DESTINATION lib${LIB_SUFFIX}) #install (FILES ${CMAKE_CURRENT_BINARY_DIR}/cygpfs.dll DESTINATION bin) install (FILES ${CMAKE_CURRENT_SOURCE_DIR}/pfs.h ${CMAKE_CURRENT_SOURCE_DIR}/array2d.h DESTINATION include/pfs) install (FILES ${CMAKE_CURRENT_BINARY_DIR}/pfs.pc DESTINATION lib${LIB_SUFFIX}/pkgconfig) #install (FILES pfsoutppm.1 DESTINATION ${MAN_DIR}) pfstools-2.2.0/src/pfs/array2d.h0000664000701400070140000002071614105165613015162 0ustar rkm38rkm38/** * @file * @brief PFS library - general 2d array interface * * All pfs::Array2D classes are part of pfs library. However, to * lessen coupling of the code with pfs library, Array2D classes are * declared in this separate file. Therefore it is possible to write * the code that implements or uses Array2D interface while it has no * knowledge of other pfs library classes. * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Rafal Mantiuk and Grzegorz Krawczyk * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Rafal Mantiuk, * * $Id: array2d.h,v 1.1 2005/06/15 13:36:55 rafm Exp $ */ #ifndef Array2D_H #define Array2D_H #include namespace pfs { /** * @brief Interface for 2 dimensional array of floats. * * This is a thin interface of classes that hold 2 dimensional arrays * of floats. The interface lets to access all types of arrays in the * same way, regardless how data is really stored (row major, column * major, 2D array, array of pointers, etc.). It also simplifies * indexing. * * See also implementing classes. */ class Array2D { public: /** * Get number of columns or, in case of an image, width. */ virtual int getCols() const = 0; /** * Get number of rows or, in case of an image, height. */ virtual int getRows() const = 0; /** * Access an element of the array for reading and * writing. Whether the given row and column are checked against * array bounds depends on an implementing class. * * Note, that if an Array2D object is passed as a pointer (what * is usually the case), to access its elements, you have to use * somewhat strange syntax: (*array)(row, column). * * @param col number of a column (x) within the range 0..(getCols()-1) * @param row number of a row (y) within the range 0..(getRows()-1) */ virtual float& operator()( int col, int row ) = 0; /** * Access an element of the array for reading. Whether the given * row and column are checked against array bounds depends on an * implementing class. * * Note, that if an Array2D object is passed as a pointer (what * is usually the case), to access its elements, you have to use * somewhat strange syntax: (*array)(row, column). * * @param col number of a column (x) within the range 0..(getCols()-1) * @param row number of a row (y) within the range 0..(getRows()-1) */ virtual const float& operator()( int col, int row ) const = 0; /** * Access an element of the array for reading and writing. This * is probably faster way of accessing elements than * operator(col, row). However there is no guarantee on the * order of elements as it usually depends on an implementing * class. The only assumption that can be make is that there are * exactly columns*rows elements and they are all unique. * * Whether the given index is checked against array bounds * depends on an implementing class. * * Note, that if an Array2D object is passed as a pointer (what * is usually the case), to access its elements, you have to use * somewhat strange syntax: (*array)(index). * * @param index index of an element within the range 0..(getCols()*getRows()-1) */ virtual float& operator()( int index ) = 0; /** * Access an element of the array for reading. This * is probably faster way of accessing elements than * operator(col, row). However there is no guarantee on the * order of elements as it usually depends on an implementing * class. The only assumption that can be make is that there are * exactly columns*rows elements and they are all unique. * * Whether the given index is checked against array bounds * depends on an implementing class. * * Note, that if an Array2D object is passed as a pointer (what * is usually the case), to access its elements, you have to use * somewhat strange syntax: (*array)(index). * * @param index index of an element within the range 0..(getCols()*getRows()-1) */ virtual const float& operator()( int index ) const = 0; /** * Each implementing class should provide its own destructor. */ virtual ~Array2D() { } }; /** * @brief Two dimensional array of floats * * Holds 2D data in column-major oder. Allows easy indexing * and retrieving array dimensions. */ class Array2DImpl: public Array2D { float *data; int cols, rows; public: Array2DImpl( int cols, int rows ) : cols( cols ), rows( rows ) { data = new float[cols*rows]; } ~Array2DImpl() { delete[] data; } inline int getCols() const { return cols; } inline int getRows() const { return rows; } inline float& operator()( int col, int row ) { assert( col >= 0 && col < cols ); assert( row >= 0 && row < rows ); return data[ col+row*cols ]; } inline const float& operator()( int col, int row ) const { assert( col >= 0 && col < cols ); assert( row >= 0 && row < rows ); return data[ col+row*cols ]; } inline float& operator()( int index ) { assert( index >= 0 && index < rows*cols ); return data[index]; } inline const float& operator()( int index ) const { assert( index >= 0 && index <= rows*cols ); return data[index]; } bool operator==(Array2D& other) const { if (other.getCols() != cols || other.getRows() != rows){ return false; } else{ for (int i = 0; igetRows() == to->getRows() ); assert( from->getCols() == to->getCols() ); const int elements = from->getRows()*from->getCols(); for( int i = 0; i < elements; i++ ) (*to)(i) = (*from)(i); } /** * Set all elements of the array to a give value. * * @param array array to modify * @param value all elements of the array will be set to this value */ inline void setArray(Array2D *array, const float value ) { const int elements = array->getRows()*array->getCols(); for( int i = 0; i < elements; i++ ) (*array)(i) = value; } /** * Perform element-by-element multiplication: z = x * y. z can be the same as x or y. * * @param z array where the result is stored * @param x first element of the multiplication * @param y second element of the multiplication */ inline void multiplyArray(Array2D *z, const Array2D *x, const Array2D *y) { assert( x->getRows() == y->getRows() ); assert( x->getCols() == y->getCols() ); assert( x->getRows() == z->getRows() ); assert( x->getCols() == z->getCols() ); const int elements = x->getRows()*x->getCols(); for( int i = 0; i < elements; i++ ) (*z)(i) = (*x)(i) * (*y)(i); } } #endif pfstools-2.2.0/src/pfs/colorspace.cpp0000664000701400070140000004211514105165613016300 0ustar rkm38rkm38/** * @brief PFS library - color space transformations * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Rafal Mantiuk and Grzegorz Krawczyk * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Rafal Mantiuk, * * $Id: colorspace.cpp,v 1.7 2014/04/05 22:04:13 rafm Exp $ */ #include #include "pfs.h" #include #include #include #include namespace pfs { const float Lmax = 10000.f; const float n = 0.15930175781250000f; const float m = 78.843750000000000f; const float c1 = 0.83593750000000000f; const float c2 = 18.851562500000000f; const float c3 = 18.687500000000000f; //--- 7 digits approximation of precise values static const float rgb2xyzD65Mat[3][3] = { { 0.412424f, 0.357579f, 0.180464f }, { 0.212656f, 0.715158f, 0.072186f }, { 0.019332f, 0.119193f, 0.950444f } }; static const float xyz2rgbD65Mat[3][3] = { { 3.240708f, -1.537259f, -0.498570f }, { -0.969257f, 1.875995f, 0.041555f }, { 0.055636f, -0.203996f, 1.057069f } }; static const float ycbcr2rgb_bt709[3][3] = { { 1.f, 0.f, 1.5748f }, { 1.f, -0.18733f, -0.46813f }, { 1.f, 1.85563f, 0.f } }; static const float ycbcr2rgb_bt2020[3][3] = { { 1.f, 0.f, 1.47460f }, { 1.f, -0.16455f, -0.57135f }, { 1.f, 1.88140f, 0.f } }; static const float rgb2020_to_xyz[3][3] = { { 0.6370f, 0.1446f, 0.1689f }, { 0.2627f, 0.6780f, 0.0593f }, { 0.0000f, 0.0281f, 1.0610f } }; static const float xyz_to_rgb2020[3][3] = {{1.71650250836063f, -0.355584689096764f, -0.25337521357085f}, {-0.666625609145029f, 1.61644656652221f, 0.0157754797265114f}, {0.017655211703087f, -0.0428106960596362, 0.942089263920533f}}; static const float rgb2020_to_ycbcr[3][3] = { { 0.2627f, 0.6780f, 0.0593f }, { -0.139630063f, -0.360369937f, 0.5f}, { 0.5f, -0.459785705, -0.040214295 } }; // //--- precise values for matrix convertion (above float precission) // static const float rgb2xyzD65Mat[3][3] = // { { 0.412424, 0.357579, 0.180464 }, // { 0.212656, 0.715158, 0.0721856 }, // { 0.0193324, 0.119193, 0.950444 } }; // static const float xyz2rgbD65Mat[3][3] = // { { 3.24071, -1.53726, -0.498571 }, // { -0.969258, 1.87599, 0.0415557 }, // { 0.0556352, -0.203996, 1.05707 } }; // //--- original values which lead to mean sq error of above 3 for green channel // static const float rgb2xyzD65Mat[3][3] = // { { 0.4124f, 0.3576f, 0.1805f }, // { 0.2126f, 0.7152f, 0.0722f }, // { 0.0193f, 0.1192f, 0.9505f } }; // static const float xyz2rgbD65Mat[3][3] = // { { 3.2406f, -1.5372f, -0.4986f }, // { -0.9689f, 1.8758f, 0.0415f }, // { 0.0557f, -0.2040f, 1.0570f } }; static void multiplyByMatrix( const Array2D *inC1, const Array2D *inC2, const Array2D *inC3, Array2D *outC1, Array2D *outC2, Array2D *outC3, const float mat[3][3] ) { int imgSize = inC1->getRows()*inC1->getCols(); for( int index = 0; index < imgSize ; index++ ) { const float x1 = (*inC1)(index), x2 = (*inC2)(index), x3 = (*inC3)(index); float &y1 = (*outC1)(index), &y2 = (*outC2)(index), &y3 = (*outC3)(index); y1 = mat[0][0]*x1 + mat[0][1]*x2 + mat[0][2]*x3; y2 = mat[1][0]*x1 + mat[1][1]*x2 + mat[1][2]*x3; y3 = mat[2][0]*x1 + mat[2][1]*x2 + mat[2][2]*x3; } } //----------------------------------------------------------- // sRGB conversion functions //----------------------------------------------------------- static inline float clamp( const float v, const float min, const float max ) { if( v < min ) return min; if( v > max ) return max; return v; } static void transformSRGB2XYZ( const Array2D *inC1, const Array2D *inC2, const Array2D *inC3, Array2D *outC1, Array2D *outC2, Array2D *outC3 ) { int imgSize = inC1->getRows()*inC1->getCols(); for( int index = 0; index < imgSize ; index++ ) { float r = (*inC1)(index), g = (*inC2)(index), b = (*inC3)(index); float &x = (*outC1)(index), &y = (*outC2)(index), &z = (*outC3)(index); r = clamp( r, 0, 1 ); g = clamp( g, 0, 1 ); b = clamp( b, 0, 1 ); x = (r <= 0.04045 ? r / 12.92f : powf( (r + 0.055f) / 1.055f, 2.4f ) ); y = (g <= 0.04045 ? g / 12.92f : powf( (g + 0.055f) / 1.055f, 2.4f ) ); z = (b <= 0.04045 ? b / 12.92f : powf( (b + 0.055f) / 1.055f, 2.4f ) ); } multiplyByMatrix( outC1, outC2, outC3, outC1, outC2, outC3, rgb2xyzD65Mat ); } static void transformXYZ2SRGB( const Array2D *inC1, const Array2D *inC2, const Array2D *inC3, Array2D *outC1, Array2D *outC2, Array2D *outC3 ) { multiplyByMatrix( outC1, outC2, outC3, outC1, outC2, outC3, xyz2rgbD65Mat ); int imgSize = inC1->getRows()*inC1->getCols(); for( int index = 0; index < imgSize ; index++ ) { float r = (*inC1)(index), g = (*inC2)(index), b = (*inC3)(index); float &o_r = (*outC1)(index), &o_g = (*outC2)(index), &o_b = (*outC3)(index); r = clamp( r, 0, 1 ); g = clamp( g, 0, 1 ); b = clamp( b, 0, 1 ); o_r = (r <= 0.0031308f ? r *= 12.92f : 1.055f * powf( r, 1./2.4 ) - 0.055f); o_g = (g <= 0.0031308f ? g *= 12.92f : 1.055f * powf( g, 1./2.4 ) - 0.055f); o_b = (b <= 0.0031308f ? b *= 12.92f : 1.055f * powf( b, 1./2.4 ) - 0.055f); } } static void transformXYZ2Yuv( const Array2D *inC1, const Array2D *inC2, const Array2D *inC3, Array2D *outC1, Array2D *outC2, Array2D *outC3 ) { int imgSize = inC1->getRows()*inC1->getCols(); for( int index = 0; index < imgSize ; index++ ) { const float &X = (*inC1)(index), Y = (*inC2)(index), &Z = (*inC3)(index); float &outY = (*outC1)(index), &u = (*outC2)(index), &v = (*outC3)(index); float x = X/(X+Y+Z); float y = Y/(X+Y+Z); // assert((4.f*nx / (-2.f*nx + 12.f*ny + 3.f)) <= 0.62 ); // assert( (9.f*ny / (-2.f*nx + 12.f*ny + 3.f)) <= 0.62 ); u = 4.f*x / (-2.f*x + 12.f*y + 3.f); v = 9.f*y / (-2.f*x + 12.f*y + 3.f); outY = Y; } } static void transformYuv2XYZ( const Array2D *inC1, const Array2D *inC2, const Array2D *inC3, Array2D *outC1, Array2D *outC2, Array2D *outC3 ) { int imgSize = inC1->getRows()*inC1->getCols(); for( int index = 0; index < imgSize ; index++ ) { const float Y = (*inC1)(index), &u = (*inC2)(index), &v = (*inC3)(index); float &X = (*outC1)(index), &outY = (*outC2)(index), &Z = (*outC3)(index); float x = 9.f*u / (6.f*u - 16.f*v + 12.f); float y = 4.f*v / (6.f*u - 16.f*v + 12.f); X = x/y * Y; Z = (1.f-x-y)/y * Y; outY = Y; } } static void transformYxy2XYZ( const Array2D *inC1, const Array2D *inC2, const Array2D *inC3, Array2D *outC1, Array2D *outC2, Array2D *outC3 ) { int imgSize = inC1->getRows()*inC1->getCols(); for( int index = 0; index < imgSize ; index++ ) { const float Y = (*inC1)(index), x = (*inC2)(index), y = (*inC3)(index); float &X = (*outC1)(index), &outY = (*outC2)(index), &Z = (*outC3)(index); X = x/y * Y; Z = (1.f-x-y)/y * Y; outY = Y; } } static void transformXYZ2Yxy( const Array2D *inC1, const Array2D *inC2, const Array2D *inC3, Array2D *outC1, Array2D *outC2, Array2D *outC3 ) { int imgSize = inC1->getRows()*inC1->getCols(); for( int index = 0; index < imgSize ; index++ ) { const float X = (*inC1)(index), Y = (*inC2)(index), Z = (*inC3)(index); float &outY = (*outC1)(index), &x = (*outC2)(index), &y = (*outC3)(index); x = X/(X+Y+Z); y = Y/(X+Y+Z); outY = Y; } } static void transformRGB2XYZ( const Array2D *inC1, const Array2D *inC2, const Array2D *inC3, Array2D *outC1, Array2D *outC2, Array2D *outC3 ) { multiplyByMatrix( inC1, inC2, inC3, outC1, outC2, outC3, rgb2xyzD65Mat ); } static void transformXYZ2RGB( const Array2D *inC1, const Array2D *inC2, const Array2D *inC3, Array2D *outC1, Array2D *outC2, Array2D *outC3 ) { multiplyByMatrix( inC1, inC2, inC3, outC1, outC2, outC3, xyz2rgbD65Mat ); } float pq2linFunction(float in){ double v_t = powf( std::max(in,0.f), 1.f/m ); return Lmax * powf(std::max( v_t-c1, (double) 0.f )/(c2-c3*v_t), 1.f/n); } static void transform_pq2lin( const Array2D *inC1, const Array2D *inC2, const Array2D *inC3, Array2D *outC1, Array2D *outC2, Array2D *outC3 ) { int imgSize = inC1->getRows()*inC1->getCols(); for( int index = 0; index < imgSize ; index++ ) { (*outC1)(index) = pq2linFunction((*inC1)(index)); (*outC2)(index) = pq2linFunction((*inC2)(index)); (*outC3)(index) = pq2linFunction((*inC3)(index)); } } float lin2pqFunction(float in){ in = std::max(in, 0.0f); double L_n_o = pow(((double) in)/Lmax, n); return pow((c1+c2*L_n_o)/(1+c3*L_n_o), m); } static void transform_lin2pq( const Array2D *inC1, const Array2D *inC2, const Array2D *inC3, Array2D *outC1, Array2D *outC2, Array2D *outC3 ) { int imgSize = inC1->getRows()*inC1->getCols(); for (int i = 0; i< imgSize; i++){ (*outC1)(i) = lin2pqFunction((*inC1)(i)); (*outC2)(i) = lin2pqFunction((*inC2)(i)); (*outC3)(i) = lin2pqFunction((*inC3)(i)); } } float HLG_OETF(float in){ float a = 0.17883277; float b = 1-4*a; float c = 0.5-a*log(4*a); float r = 0.5; float normE = 10000.0/12.0; in = in/normE; if(in <= 1.0){ return r*sqrt(in); } else{ return a*log(in - b) + c; } } float HLG_inverse_OETF(float in){ float a = 0.17883277; float b = 1-4*a; float c = 0.5-a*log(4*a); float r = 0.5; float normE = 10000.0/12.0; float rtn = 0; if(in<=r){ rtn = pow(in/r,2); } else{ rtn = exp((in-c)/a) + b; } return rtn*normE; } static void hlg2lin( const Array2D *inC1, const Array2D *inC2, const Array2D *inC3, Array2D *outC1, Array2D *outC2, Array2D *outC3, const float L_w ){ int imgSize = inC1->getRows()*inC1->getCols(); for (int i = 0; igetCols()*inC1->getRows(); for(int i = 0; igetCols() == inC2->getCols() && inC2->getCols() == inC3->getCols() && inC3->getCols() == outC1->getCols() && outC1->getCols() == outC2->getCols() && outC2->getCols() == outC3->getCols() ); assert( inC1->getRows() == inC2->getRows() && inC2->getRows() == inC3->getRows() && inC3->getRows() == outC1->getRows() && outC1->getRows() == outC2->getRows() && outC2->getRows() == outC3->getRows() ); CSTransEdge *gotByEdge[ CS_LAST ] = { NULL }; // Breadth First Search std::list bfsList; bfsList.push_back( inCS ); bool found = false; while( !bfsList.empty() ) { ColorSpace node = bfsList.front(); bfsList.pop_front(); // std::cerr << "Graph Node: " << node << "\n"; if( node == outCS ) { found = true; break; } for( CSTransEdge *edge = CSTransGraph[node]; edge != NULL; edge = edge->next ) { if( edge->destCS != inCS && gotByEdge[ edge->destCS ] == NULL ) { bfsList.push_back( edge->destCS ); gotByEdge[ edge->destCS ] = edge; } } } if( !found ) { // TODO: All transforms should be supported throw Exception( "Not supported color transform" ); } else { // Reverse path std::list step; ColorSpace currentNode = outCS; while( currentNode != inCS ) { // std::cerr << "edge: " << gotByEdge[ currentNode ]->srcCS << " -- " // << gotByEdge[ currentNode ]->destCS << "\n"; step.push_front( gotByEdge[ currentNode ] ); currentNode = gotByEdge[ currentNode ]->srcCS; } // Execute path std::list::iterator it; for( it = step.begin(); it != step.end(); it++ ) { // std::cerr << "edge: " << (*it)->srcCS << " -- " // << (*it)->destCS << "\n"; if( it == step.begin() ) (*it)->func( inC1, inC2, inC3, outC1, outC2, outC3 ); else (*it)->func( outC1, outC2, outC3, outC1, outC2, outC3 ); } } } } pfstools-2.2.0/src/pfs/pfs.h0000664000701400070140000004664714105165613014421 0ustar rkm38rkm38/** * @file * @brief PFS library - core API interfaces * * Classes for reading and writing a stream of PFS frames. * * Note on the design of pfs library API: pfs library API makes * extensive usage of interfaces - classes that have only virtual * methods. This way no private fields are visible for the client * programs. Everything that is private is hidden in .cpp file rather * than the header .h. For example, pfs library uses STL to store some * internal data, but no STL class can be found the header file * pfs.h. Such design should hopefully give less problems when * extending and updating the library. * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Rafal Mantiuk and Grzegorz Krawczyk * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Rafal Mantiuk, * * $Id: pfs.h,v 1.5 2014/04/05 22:04:13 rafm Exp $ */ #ifndef PFS_H #define PFS_H #include #include #include "array2d.h" #include //struct option; /** * All classes and function from PFS library reside in pfs namespace. */ namespace pfs { /** * Utility class that keeps pointer and deletes pointed object * when the class is deleted. * * Note that it is not a full implementation of the smart pointer * and memory management is not fool proof. You should never store * this object as a global variable or a field of a class. These * objects should be used only as local variables. */ template class SelfDestructPtr { T *ptr; mutable bool itsOwn; public: explicit SelfDestructPtr( T *ptr = 0 ): ptr(ptr), itsOwn(ptr!=0) { } SelfDestructPtr( const SelfDestructPtr& r ) : itsOwn(r.itsOwn), ptr(r.release()) {} SelfDestructPtr& operator=( const SelfDestructPtr& r ) { if (&r != this) { if (ptr != r.ptr) { if( itsOwn ) delete ptr; itsOwn = r.itsOwn; } else if( r.itsOwn ) itsOwn = true; ptr = r.release(); } return *this; } ~SelfDestructPtr() { if( itsOwn ) delete ptr; } bool operator==( const SelfDestructPtr &x ) const { return *(ptr) == *(x.ptr); } bool operator!=( const SelfDestructPtr &x ) const { return *(ptr) != *(x.ptr); } T& operator*() const {return *ptr;} T* operator->() const {return ptr;} T* get() const {return ptr;} T* release() const {itsOwn = false; return ptr;} }; /** * Iterator that allows to get the list of available tags in a * TagContainer. */ class TagIterator { public: /** * Get next item on the list. * * @return name of the tag */ virtual const char *getNext() = 0; /** * Returns true if there is still an item left on the list. */ virtual bool hasNext() const = 0; virtual ~TagIterator() { } }; typedef SelfDestructPtr TagIteratorPtr; /** * TagContainer interface allows to read and modify tags. A tag is "name"="value" pair. */ class TagContainer { public: /** * Get a string tag of the name tagName from the TagContainer. * @param tagName name of the tag to retrieve * @return tag value or NULL if tag was not found */ virtual const char* getString( const char *tagName ) = 0; /** * Set or add a string tag of the name tagName. * @param tagName name of the tag to add or set * @param tagValue value of the tag */ virtual void setString( const char *tagName, const char *tagValue ) = 0; /** * Removes (if exists) a tag of the name tagName from the TagContainer. * @param tagName name of the tag to remove */ virtual void removeTag( const char *tagName ) = 0; /** * Use TagIterator to iterate over all tags in the TagContainer. * TagIteratorPtr is a smart pointer, which destructs * TagIterator when TagIteratorPtr is destructed. Use -> * operator to access TagIterator members from a TagIteratorPtr * object. * * To iterate over all tags, use the following code: * * pfs::TagIteratorPtr it( frame->getTags()->getIterator() ); * while( it->hasNext() ) { * const char *tagName = it->getNext(); * //Do something * } * */ virtual TagIteratorPtr getIterator() const = 0; }; /** * Channel interface represents a 2D rectangular array with * associated tags. */ class Channel : public Array2D { public: /** * Gets width of the channel (in pixels). * This is a synonym for Array2D::getCols(). */ int getWidth() const { return getCols(); } /** * Gets height of the channel (in pixels). * This is a synonym for Array2D::getRows(). */ virtual int getHeight() const { return getRows(); } /** * Gets name of the channel. */ virtual const char *getName() const = 0; /** * Returns TagContainer that can be used to access or modify * tags associated with this Channel object. */ virtual TagContainer *getTags() = 0; /** * For performance reasons, the channels can be accessed as a * table of float values. Data is given in row-major order, i.e. * it is indexed data[x+y*width]. If performance is not crucial, * use Array2D interface instead. * * @return a table of floats of the size width*height */ virtual float *getRawData() = 0; }; /** * Iterator that allows to get the list of available channels in a frame. */ class ChannelIterator { public: /** * Get next item on the list. */ virtual Channel *getNext() = 0; /** * Returns true if there is still an item left on the list. */ virtual bool hasNext() const = 0; virtual ~ChannelIterator() { } }; typedef SelfDestructPtr ChannelIteratorPtr; /** * Interface representing a single PFS frame. Frame may contain 0 * or more channels (e.g. color XYZ, depth channel, alpha * channnel). All the channels are of the same size. Frame can * also contain additional information in tags (see getTags). */ class Frame { public: /** * Gets width of the channels (in pixels). */ virtual int getWidth() const = 0; /** * Gets height of the channels (in pixels). */ virtual int getHeight() const = 0; /** * Gets color channels in XYZ color space. May return NULLs * if such channels do not exist. Values assigned to * X, Y, Z are always either all NULLs or valid pointers to * channels. * * @param X [out] a pointer to store X channel in * @param Y [out] a pointer to store Y channel in * @param Z [out] a pointer to store Z channel in */ virtual void getXYZChannels( Channel* &X, Channel* &Y, Channel* &Z ) = 0; /** * Creates color channels in XYZ color space. If such channels * already exists, returns existing channels, rather than * creating new ones. Note, that nothing can be assumed about * the content of each channel. * * @param X [out] a pointer to store X channel in * @param Y [out] a pointer to store Y channel in * @param Z [out] a pointer to store Z channel in */ virtual void createXYZChannels( Channel* &X, Channel* &Y, Channel* &Z ) = 0; /** * Gets a named channel. * * @param name [in] name of the channel. Name must be 8 or less * character long. * @return channel or NULL if the channel does not exist */ virtual Channel* getChannel( const char *name ) = 0; /** * Creates a named channel. If the channel already exists, returns * existing channel. * * Note that new channels should be created only for the first * frame. The channels should not changes for the subsequent * frames of a sequence. * * @param name [in] name of the channel. Name must be 8 or less * character long. * @return existing or newly created channel */ virtual Channel* createChannel( const char *name ) = 0; /** * Removes a channel and frees the memory allocated for it. It is * safe to remove the channel pointed by the ChannelIterator. * * @param channel [in] channel that should be removed. */ virtual void removeChannel( Channel *channel ) = 0; /** * DEPRECIATED!!! Use getIterator instead. * * Returns iterator for all available channels. * * Note that only one iterator for particular frame can be used at * a time. This method returns each time the same data structure, * so the iterator from previous call is lost after the call. The * iterator MUST NOT be deleted after use. * * Object ChannelIterator MUST NOT be freed. It's responsibility * of a Frame object. */ virtual ChannelIterator *getChannels() = 0; /** * Use ChannelIterator to iterate over all Channels in the Frame. * ChannelIteratorPtr is a smart pointer, which destructs * ChannelIterator when ChannelIteratorPtr is destructed. Use -> * operator to access ChannelIterator members from a * ChannelIteratorPtr object. * * To iterate over all channels, use the following code: * * pfs::ChannelIteratorPtr it( frame->getChannelIterator() ); * while( it->hasNext() ) { * pfs::Channel *ch = cit->getNext(); * //Do whatever is needed * } * */ virtual ChannelIteratorPtr getChannelIterator() = 0; /** * Returns TagContainer that can be used to access or modify * tags associated with this Frame object. */ virtual TagContainer *getTags() = 0; virtual ~Frame() = 0; }; /** * Copy all tags from both the frame and its channels to the * destination frame. If there is no corresponding destination * channel for a source channel, the tags from that source channel * will not be copied. Note, that all tags in the destination * channel will be removed before copying. Therefore after this * operation, the destination will contain exactly the same tags as * the source. */ void copyTags( Frame *from, Frame *to ); /** * Copy all tags from one container into another. Note, that all * tags in the destination channel will be removed before * copying. Therefore after this operation, the destination will * contain exactly the same tags as the source. */ void copyTags( const TagContainer *from, TagContainer *to ); class DOMIOImpl; /** * Reading and writing frames in PFS format from/to streams. */ class DOMIO { DOMIOImpl *impl; public: DOMIO(); ~DOMIO(); /** * Creates a frame that can be latter written to the stream * using writeFrame method. This method and readFrame are the * only way to create Frame objects. * * Note: Frame object must be released with freeFrame methods * as soon as it is no longer needed. Otherwise the * application will run out of memory. * * @param width width of the frame to create * @param height height of the frame to create * @return Frame object that can be modified and written back to PFS * stream using writeFrame method */ Frame *createFrame( int width, int height ); /** * Read PFS frame from the input Stream. This method and * createFrame are the only way to create Frame objects. * * Note: Frame object must be released with freeFrame methods * as soon as it is no longer needed. Otherwise the * application will run out of memory. * * @param inputStream read frame from that stream * @return Frame object that contains PFS frame read from * the stream. NULL if there are no more frames. */ Frame *readFrame( FILE *inputStream ); /** * Writes Frame object to outputStream in PFS format. * * @param frame Frame object to be written. This object * must be created with readFrame or createFrame method. * @param outputStream write frame to that stream */ void writeFrame( Frame *frame, FILE *outputStream ); /** * Deallocated memory allocated with createFrame or readFrame. Must * be called as soon as frame is not needed. Pointer to a frame is * invalid after this method call. * * @param frame Frame object to be freed */ void freeFrame( Frame *frame ); }; /** * A pair of a file name and file handler, returned from * FrameFileIterator. */ struct FrameFile { FrameFile( FILE *fh, const char* fileName ): fh(fh), fileName( fileName ) { } /** * File handler. */ FILE *fh; /** * File name. */ const char *fileName; }; class FrameFileIteratorImpl; /** * Utility class that can be used to iterate over file names * specified as command line arguments. It can handle patterns, * like frame%04d.hdr, where %04d is replaced with specified * range of frame numbers. * */ class FrameFileIterator { FrameFileIteratorImpl *impl; public: /** * Creates new iterator over frame files. Command line * arguments are parsed and all recognized arguments are * removed. * * @param argc argument count passed to program's main function. * @param argv argument values passed to program's main function. * @param fopenMode mode used to fopen frame files, usually "rb" or "wb" * @param fileNamePrefix each frame pattern must be preceded * with this string (for example "-i'). If NULL, every argument that * does not start with "-" is treated as a frame pattern. * @param stdinout if set, treat '-' file name specially and instead * of opening a named file, use filedescriptor passed as this parameter. * It should be used to get or write data to stdin / stdout. * @param optstring parameter string passed to getopt() * function. When optstring != NULL, FrameFileIterator will skip * all parameters and their required arguments. Optional * arguments are not handled. * @param getopt_long parameter structure passed to getopt_long() * function. When getopt_long != NULL, FrameFileIterator will skip * all parameters and their required arguments. Optional * arguments are not handled. * @throws CommandLineException on bad syntax of command line options */ FrameFileIterator( int &argc, char* argv[], const char *fopenMode, const char *fileNamePrefix = NULL, FILE *stdinout = NULL, const char *optstring = NULL, const struct option *getopt_long = NULL ); ~FrameFileIterator(); /** * Get the file handle FILE* and file name for the next * frame. Note that fileName string is valid until next * call to getNextFrameFile or closeFrameFile. * * When file handle is no longer needed, closeFileFile * should be called. * * @return file handle FILE* and file name of the next frame. * Returns file handle == NULL if there are no more frames. * * @throws Exception if the file is not found */ FrameFile getNextFrameFile( ); /** * Close file openned with getNextFrameFile. * * @param frameFile FrameFile object returned from getNextFrameFile */ void closeFrameFile( FrameFile &frameFile ); static void printUsage( FILE *out, const char *progName ); }; /// This enum is used to specify color spaces for transformColorSpace function enum ColorSpace { CS_XYZ = 0, ///< Absolute XYZ space, reference white - D65, Y is calibrated luminance in cd/m^2 CS_RGB, ///< Absolute XYZ space, reference white - D65, Rec. 709 CS_SRGB, ///< sRGB color space for LDR images (see ///www.srgb.com). The possible pixel values ///for R, G and B channel should be within ///range 0-1 (the values above or below this ///range will be clamped). Peak luminance ///level of the display is 80cd/m^2. CS_YUV, ///< Perceptually uniform u and v color coordinates, Y is calibrated luminance in cd/m^2 CS_Yxy, ///< Luminance and normalized chromacities (x=X/(X+Y+Z), y=Y/(X+Y+Z)) CS_INVALID, ///< For convenience CS_PQYCbCr2020, ///< MPEG YCbCr with the PQ transfer function and Rec. 2020 colorspace CS_YCbCr709, ///< MPEG YCbCr with the sRGB non-linearity and Rec. 709 colorspace CS_HLGYCbCr2020, CS_RGB2020, ///< Rec 2020 wide color gamut RGB colorspace CS_LAST ///< For internal purposes only }; /** * Transform color channels from one color space into * another. Input and output channels may point to the same data * for in-memory transform. * * @param inCS input color space * @param inC1 first color channel of the input image * @param inC2 second color channel of the input image * @param inC3 third color channel of the input image * @param outCS output color space * @param outC1 first color channel of the output image * @param outC2 second color channel of the output image * @param outC3 third color channel of the output image */ void transformColorSpace( ColorSpace inCS, const Array2D *inC1, const Array2D *inC2, const Array2D *inC3, ColorSpace outCS, Array2D *outC1, Array2D *outC2, Array2D *outC3 ); /** * General exception class used to throw exceptions from pfs library. */ class Exception { char msg[1024]; public: /** * Creates a new exception. * * @param message description of the cause for the * exception. The copy of the message string is made, so it can * be freed after creating Exception. */ Exception( const char* const message ) { strcpy( msg, message ); } ~Exception() {}; /** * Returns the description of the problem. * * @return text description of the cause for the exception */ const char* getMessage() { return msg; } }; /** * Exception class used to throw exceptions from FileFileIterator class. */ class CommandLineException: public Exception { public: CommandLineException( const char* const message ): Exception( message ) { } }; } #endif pfstools-2.2.0/src/pfs/pfs.pc.in0000664000701400070140000000036314105165613015162 0ustar rkm38rkm38prefix=@prefix@ exec_prefix=${prefix} libdir=${prefix}/lib includedir=${prefix}/include Name: PFS Description: Library for manipulating pfs image format Requires: Version: @PACKAGE_VERSION@ Libs: -L${libdir} -lpfs Cflags: -I${includedir}/pfs pfstools-2.2.0/src/pfs/pfs.cpp0000664000701400070140000004204214105165613014735 0ustar rkm38rkm38/** * @brief PFS library - core API interfaces * * Classes for reading and writing a stream of PFS frames. * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Rafal Mantiuk and Grzegorz Krawczyk * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Rafal Mantiuk, * * $Id: pfs.cpp,v 1.13 2014/09/17 15:07:18 rafm Exp $ */ #if !defined(_MSC_VER) && !defined(_MATLAB_VER) #include #endif // This does not seem to be needed under Cygwin #if defined(_MSC_VER) || defined(__MINGW64__) || defined(__MINGW32__) #include #define HAVE_SETMODE #endif #include #include #include #include #include #include #include #include "pfs.h" #define PFSEOL "\x0a" #define PFSEOLCH '\x0a' #define MAX_RES 65535 #define MAX_CHANNEL_NAME 32 #define MAX_TAG_STRING 1024 #define MAX_CHANNEL_COUNT 1024 using namespace std; namespace pfs { const char *PFSFILEID="PFS1\x0a"; //------------------------------------------------------------------------------ // TagContainer implementation //------------------------------------------------------------------------------ typedef list TagList; class TagIteratorImpl: public TagIterator { TagList::const_iterator it; const TagList &tagList; string tagName; public: TagIteratorImpl( const TagList &tagList ) : tagList( tagList ) { it = tagList.begin(); } /** * Get next item on the list. */ const char *getNext() { const string &tag = *(it++); size_t equalSign = tag.find( '=' ); assert( equalSign != -1 ); tagName = string( tag, 0, equalSign ); return tagName.c_str(); } /** * Returns true if there is still an item left on the list. */ bool hasNext() const { return it != tagList.end(); } }; class TagContainerImpl: public TagContainer { public: private: TagList tagList; public: // ~TagContainerImpl() // { // tagList.clear(); // } TagList::const_iterator tagsBegin() const { return tagList.begin(); } TagList::const_iterator tagsEnd() const { return tagList.end(); } int getSize() const { return (int)tagList.size(); } void appendTagEOL( const char *tagValue ) { assert( tagValue[strlen( tagValue ) -1] == PFSEOLCH ); tagList.push_back( string( tagValue, strlen( tagValue ) -1 ) ); } void appendTag( const string &tagValue ) { tagList.push_back( tagValue ); } TagList::iterator findTag( const char *tagName ) { size_t tagNameLen = strlen( tagName ); TagList::iterator it; for( it = tagList.begin(); it != tagList.end(); it++ ) { if( !memcmp( tagName, it->c_str(), tagNameLen ) ) break; // Found } return it; } void setTag( const char *tagName, const char *tagValue ) { string tagVal( tagName ); tagVal += "="; tagVal += tagValue; TagList::iterator element = findTag( tagName ); if( element == tagList.end() ) { // Does not exist tagList.push_back( tagVal ); } else { // Already exist *element = tagVal; } } const char *getTag( const char *tagName ) { TagList::iterator element = findTag( tagName ); if( element == tagList.end() ) return NULL; string::size_type equalSign = element->find( '=' ); assert( equalSign != string::npos ); return element->c_str() + equalSign + 1; } //Implementation of TagContainer const char* getString( const char *tagName ) { return getTag( tagName ); } void setString( const char *tagName, const char *tagValue ) { setTag( tagName, tagValue ); } void removeTag( const char *tagName ) { TagList::iterator element = findTag( tagName ); if( element != tagList.end() ) tagList.erase( element ); } TagIteratorPtr getIterator() const { return TagIteratorPtr( new TagIteratorImpl( tagList ) ); } void removeAllTags() { tagList.clear(); } }; void copyTags( const TagContainer *from, TagContainer *to ) { TagContainerImpl *f = (TagContainerImpl*)from; TagContainerImpl *t = (TagContainerImpl*)to; t->removeAllTags(); TagList::const_iterator it; for( it = f->tagsBegin(); it != f->tagsEnd(); it++ ) { t->appendTag( *it ); } } void copyTags( Frame *from, Frame *to ) { copyTags( from->getTags(), to->getTags() ); pfs::ChannelIterator *it = from->getChannels(); while( it->hasNext() ) { pfs::Channel *fromCh = it->getNext(); pfs::Channel *toCh = to->getChannel( fromCh->getName() ); if( toCh == NULL ) // Skip if there is no corresponding channel continue; copyTags( fromCh->getTags(), toCh->getTags() ); } } //------------------------------------------------------------------------------ // Channel implementation //------------------------------------------------------------------------------ class DOMIOImpl; class ChannelImpl: public Channel { int width, height; float *data; const char *name; protected: friend class DOMIOImpl; TagContainerImpl *tags; public: ChannelImpl( int width, int height, const char *n_name ) : width( width ), height( height ) { data = new float[width*height]; tags = new TagContainerImpl(); name = strdup( n_name ); } virtual ~ChannelImpl() { delete tags; delete[] data; free( (void*)name ); } // Channel implementation TagContainer *getTags() { return tags; } float *getRawData() { return data; } //Array2D implementation virtual int getCols() const { return width; } virtual int getRows() const { return height; } virtual const char *getName() const { return name; } inline float& operator()( int x, int y ) { assert( x >= 0 && x < width ); assert( y >= 0 && y < height ); return data[ x+y*width ]; } inline const float& operator()( int x, int y ) const { assert( x >= 0 && x < width ); assert( y >= 0 && y < height ); return data[ x+y*width ]; } inline float& operator()( int rowMajorIndex ) { assert( rowMajorIndex < width*height ); assert( rowMajorIndex >= 0 ); return data[ rowMajorIndex ]; } inline const float& operator()( int rowMajorIndex ) const { assert( rowMajorIndex < width*height ); assert( rowMajorIndex >= 0 ); return data[ rowMajorIndex ]; } }; //------------------------------------------------------------------------------ // Map of channels //------------------------------------------------------------------------------ struct str_cmp: public std::binary_function { bool operator()(const char* s1, const char* s2) const { return strcmp(s1, s2) < 0; } }; typedef std::map ChannelMap; //------------------------------------------------------------------------------ // Channel Iterator implementation //----------------------------------------------------------------------------- class ChannelIteratorImpl: public ChannelIterator { ChannelMap::iterator it; ChannelMap *cm; public: ChannelIteratorImpl( ChannelMap *cm ) : cm(cm) { reset(); } void reset() { it = cm->begin(); } Channel *getNext() { if( !hasNext() ) return NULL; return (it++)->second; } bool hasNext() const { return it != cm->end(); } }; //------------------------------------------------------------------------------ // Frame implementation //------------------------------------------------------------------------------ //A pure virtual destructor Frame::~Frame() {} class FrameImpl: public Frame { int width, height; protected: friend class DOMIOImpl; TagContainerImpl *tags; // enum ChannelID { CH_X = 0, CH_Y, CH_Z, CH_ALPHA, CH_DEPTH, CH_AUXLUM, CH_COUNT }; // static const char* const channelStrID[CH_COUNT]; ChannelMap channel; ChannelIteratorImpl channelIterator; public: FrameImpl( int width, int height ): width( width ), height( height ), channelIterator( &channel ) { tags = new TagContainerImpl(); } ~FrameImpl() { delete tags; ChannelMap::iterator it; for( it = channel.begin(); it != channel.end(); ) { Channel *ch = it->second; ChannelMap::iterator itToDelete = it; // Nasty trick because hashmap // elements point to string that is // freed by the channel it++; channel.erase( itToDelete ); delete ch; } } virtual int getWidth() const { return width; } virtual int getHeight() const { return height; } virtual void getXYZChannels( Channel* &X, Channel* &Y, Channel* &Z ) { if( channel.find("X") == channel.end() || channel.find("Y") == channel.end() || channel.find("Z") == channel.end() ) { X = Y = Z = NULL; } else { X = channel["X"]; Y = channel["Y"]; Z = channel["Z"]; } } virtual void createXYZChannels( Channel* &X, Channel* &Y, Channel* &Z ) { X = createChannel("X"); Y = createChannel("Y"); Z = createChannel("Z"); } Channel* getChannel( const char *name ) { ChannelMap::iterator it = channel.find(name); if( it == channel.end() ) return NULL; else return it->second; } Channel *createChannel( const char *name ) { ChannelImpl *ch; if( channel.find(name) == channel.end() ) { ch = new ChannelImpl( width, height, name ); channel.insert( pair(ch->getName(), ch) ); } else ch = channel[name]; return ch; } void removeChannel( Channel *ch ) { assert( ch != NULL ); ChannelMap::iterator it = channel.find( ch->getName() ); assert( it != channel.end() && it->second == ch ); channel.erase( it ); delete ch; } ChannelIterator *getChannels() { channelIterator.reset(); return &channelIterator; } ChannelIteratorPtr getChannelIterator() { return ChannelIteratorPtr( new ChannelIteratorImpl( &channel ) ); } TagContainer *getTags() { return tags; } }; static void readTags( TagContainerImpl *tags, FILE *in ) { int readItems; int tagCount; readItems = fscanf( in, "%d" PFSEOL, &tagCount ); if( readItems != 1 || tagCount < 0 || tagCount > 1024 ) throw Exception( "Corrupted PFS tag section: missing or wrong number of tags" ); char buf[MAX_TAG_STRING]; for( int i = 0; i < tagCount; i++ ) { char *read = fgets( buf, MAX_TAG_STRING, in ); if( read == NULL ) throw Exception( "Corrupted PFS tag section: missing tag" ); if( strlen(buf)==(MAX_TAG_STRING-1) ) { // Crop the string if needed buf[MAX_TAG_STRING-2] = PFSEOLCH; // now we need to skip all characters in the file stream up to EOL const int SKIP_BUF_LEN = 256; char skip_buf[SKIP_BUF_LEN]; do { read = fgets( skip_buf, SKIP_BUF_LEN, in ); } while( read != NULL && strlen(skip_buf)==(SKIP_BUF_LEN-1) ); } char *equalSign = strstr( buf, "=" ); if( equalSign == NULL ) throw Exception( "Corrupted PFS tag section ('=' sign missing)" ); tags->appendTagEOL( buf ); } } static void writeTags( const TagContainerImpl *tags, FILE *out ) { TagList::const_iterator it; fprintf( out, "%d" PFSEOL, tags->getSize() ); for( it = tags->tagsBegin(); it != tags->tagsEnd(); it++ ) { fputs( (const char*)(it->c_str()), out ); fprintf( out, PFSEOL ); } } //------------------------------------------------------------------------------ // pfs IO //------------------------------------------------------------------------------ class DOMIOImpl { public: Frame *readFrame( FILE *inputStream ) { assert( inputStream != NULL ); #ifdef HAVE_SETMODE // Needed under MS windows (text translation IO for stdin/out) int old_mode = setmode( fileno( inputStream ), _O_BINARY ); #endif size_t read; char buf[5]; read = fread( buf, 1, 5, inputStream ); if( read == 0 ) return NULL; // EOF if( memcmp( buf, PFSFILEID, 5 ) ) throw Exception( "Incorrect PFS file header" ); int width, height, channelCount; read = fscanf( inputStream, "%d %d" PFSEOL, &width, &height ); if( read != 2 || width <= 0 || width > MAX_RES || height <= 0 || height > MAX_RES ) throw Exception( "Corrupted PFS file: missing or wrong 'width', 'height' tags" ); read = fscanf( inputStream, "%d" PFSEOL, &channelCount ); if( read != 1 || channelCount < 0 || channelCount > MAX_CHANNEL_COUNT ) throw Exception( "Corrupted PFS file: missing or wrong 'channelCount' tag" ); FrameImpl *frame = (FrameImpl*)createFrame( width, height ); readTags( frame->tags, inputStream ); //Read channel IDs and tags // FrameImpl::ChannelID *channelID = new FrameImpl::ChannelID[channelCount]; list orderedChannel; for( int i = 0; i < channelCount; i++ ) { char channelName[MAX_CHANNEL_NAME+1], *rs; rs = fgets( channelName, MAX_CHANNEL_NAME, inputStream ); if( rs == NULL ) throw Exception( "Corrupted PFS file: missing channel name" ); size_t len = strlen( channelName ); // fprintf( stderr, "s = '%s' len = %d\n", channelName, len ); if( len < 1 || channelName[len-1] != PFSEOLCH ) throw Exception( "Corrupted PFS file: bad channel name" ); channelName[len-1] = 0; ChannelImpl *ch = (ChannelImpl*)frame->createChannel( channelName ); readTags( ch->tags, inputStream ); orderedChannel.push_back( ch ); } read = fread( buf, 1, 4, inputStream ); if( read == 0 || memcmp( buf, "ENDH", 4 ) ) throw Exception( "Corrupted PFS file: missing end of header (ENDH) token" ); //Read channels list::iterator it; for( it = orderedChannel.begin(); it != orderedChannel.end(); it++ ) { ChannelImpl *ch = *it; int size = frame->getWidth()*frame->getHeight(); read = fread( ch->getRawData(), sizeof( float ), size, inputStream ); if( read != size ) throw Exception( "Corrupted PFS file: missing channel data" ); } #ifdef HAVE_SETMODE setmode( fileno( inputStream ), old_mode ); #endif return frame; } Frame *createFrame( int width, int height ) { /* if( lastFrame != NULL && lastFrame->width() == width && lastFrame->height() == height ) { // Reuse last frame return lastFrame; } else delete lastFrame;*/ Frame *frame = new FrameImpl( width, height ); if( frame == NULL ) throw Exception( "Out of memory" ); return frame; } void writeFrame( Frame *frame, FILE *outputStream ) { assert( outputStream != NULL ); assert( frame != NULL ); #ifdef HAVE_SETMODE // Needed under MS windows (text translation IO for stdin/out) int old_mode = setmode( fileno( outputStream ), _O_BINARY ); #endif FrameImpl *frameImpl = (FrameImpl*)frame; fwrite( PFSFILEID, 1, 5, outputStream ); // Write header ID fprintf( outputStream, "%d %d" PFSEOL, (int)frame->getWidth(), (int)frame->getHeight() ); fprintf( outputStream, "%d" PFSEOL, (int)frameImpl->channel.size() ); writeTags( frameImpl->tags, outputStream ); //Write channel IDs and tags for( ChannelMap::iterator it = frameImpl->channel.begin(); it != frameImpl->channel.end(); it++ ) { fprintf( outputStream, "%s" PFSEOL, it->second->getName() ); writeTags( it->second->tags, outputStream ); } fprintf( outputStream, "ENDH"); //Write channels { for( ChannelMap::iterator it = frameImpl->channel.begin(); it != frameImpl->channel.end(); it++ ) { int size = frame->getWidth()*frame->getHeight(); fwrite( it->second->getRawData(), sizeof( float ), size, outputStream ); } } //Very important for pfsoutavi !!! fflush(outputStream); #ifdef HAVE_SETMODE setmode( fileno( outputStream ), old_mode ); #endif } void freeFrame( Frame *frame ) { delete frame; } }; DOMIO::DOMIO() { impl = new DOMIOImpl(); } DOMIO::~DOMIO() { delete impl; } Frame *DOMIO::createFrame( int width, int height ) { return impl->createFrame( width, height ); } Frame *DOMIO::readFrame( FILE *inputStream ) { return impl->readFrame( inputStream ); } void DOMIO::writeFrame( Frame *frame, FILE *outputStream ) { impl->writeFrame( frame, outputStream ); } void DOMIO::freeFrame( Frame *frame ) { impl->freeFrame( frame ); } }; pfstools-2.2.0/src/hdrhtml/0002775000701400070140000000000014105165614014314 5ustar rkm38rkm38pfstools-2.2.0/src/hdrhtml/hdrhtml_hdrlabs_templ/0002775000701400070140000000000014105165614020656 5ustar rkm38rkm38pfstools-2.2.0/src/hdrhtml/hdrhtml_hdrlabs_templ/hdrhtml_page_templ.html0000664000701400070140000002550514105165613025407 0ustar rkm38rkm38 @title@ @image_htmlcode@ pfstools-2.2.0/src/hdrhtml/hdrhtml_hdrlabs_templ/hdrhtml_assets/0002775000701400070140000000000014105165614023702 5ustar rkm38rkm38pfstools-2.2.0/src/hdrhtml/hdrhtml_hdrlabs_templ/hdrhtml_assets/information-red.png0000664000701400070140000000106614105165613027505 0ustar rkm38rkm38‰PNG  IHDR‰ tEXtSoftwareAdobe ImageReadyqÉe<ØIDATxÚ„•M(DQǽif” 5RLjd¡dcA“|l|,,°±akV(% IReE(e#v–6ÔäkRCŠ2ÌÔPSãêÿêºÞ}Ný÷Ýsÿ÷ÜsÏ=Ï* …Š\¬ÔƒJŽßÀ¸3-ð¾÷ƒIÐ j€_™{`é =Ú¸ ¬0ÀèT±ø}`Ÿ¾U&Á …¢ Ø!êCÐÚÀ7ŠrMÐIpD )(€cp¥ÌE¸öW{Á„ËåX`Œ—Q¦´yY»N=Ñr¥[Ü‚Yæ®E›÷SÃ+baÛÞÁXåqK™³._ÑK„RˆÕO`ø@9È3YÃæ¢Á€¡ÏÀèdí‰5€V—šx]ò6Ì覕¨ä[­ÛÓ’Ó<Žn>^Ä6Çe`Ðá1Ø&i™L‚WƒÓ/C¬ t€Oƒ¯h$=|N—_`G9‰Ôá5Ød¡ë& C]9ÍáÜ+‚²ñ èf¡«–£FÞÎÇ ˆiN%·ŒxžH·5Š,¥ÊßUÞs–Ž)–N!wR^£àE´Û—óø?OÑ>æ˜S.î mƒJƒ ²„ľIÜÔ`­~R*uJ•HÁiÁÄhª‰“MÆIEND®B`‚pfstools-2.2.0/src/hdrhtml/hdrhtml_hdrlabs_templ/hdrhtml_assets/hdr_viewer.css0000664000701400070140000000363114105165613026552 0ustar rkm38rkm38/* optional: full page styling with dark background */ body { background-color: #1A1C1F; font-family: sans-serif, Helvetica Narrow; color: CDCDCD; } a { color: #FF5020; text-decoration: none; } a:hover { color: #FF5020; text-decoration: underline; } /* required: HDR-Viewer styling, adjust as needed */ .hdr_viewer { background-color: #000; -moz-border-radius: 8px; -webkit-border-radius: 8px; padding: 4px 6px 4px 6px; margin: 10px; font-family: sans-serif; } .hdr_viewport { position: relative; margin: 3px 0px 3px 0px; } .hdr_controller { position: relative; height: 36px; margin: 2px 0px 2px 0px; overflow: hidden; } .knob { position: absolute; height: 36px; opacity: 0.3; filter: alpha(opacity = 30); background: #FFFFFF center center url('slider-black.png') no-repeat; cursor: pointer; -moz-border-radius: 3px; -webkit-border-radius: 3px; } div.knob:hover { background-color: #FF5020; opacity: 0.7; filter: alpha(opacity = 70); } .label { color: white; font-family: Florence, cursive; width: 110px; padding: 5px 10px 5px 10px; } .labelnumber { text-align: right; display: inline-block; width: 50px; } .hdr_help { position: absolute; right: 6px; top: 8px; opacity: 0.5; filter: alpha(opacity = 50); width: 22px; height: 22px; background: top left url('information.png') no-repeat; cursor: pointer; } div.hdr_help:hover { background: top left url('information-red.png') no-repeat; opacity: 1; } .hdr_instructions { color: #FFF; background-color: #000; } .spinner { position: absolute; opacity: 0.9; filter: alpha(opacity=90); z-index: 999; background: #000; } .spinner-img { background: url('loading-spinner.gif') no-repeat; width: 31px; height: 31px; margin: 0 auto; } pfstools-2.2.0/src/hdrhtml/hdrhtml_hdrlabs_templ/hdrhtml_assets/loading-spinner.gif0000664000701400070140000000506014105165614027461 0ustar rkm38rkm38GIF89aõÿÿÿ,,,BBBPPP\\\"""FFFddd&&&TTT^^^NNN222VVV***ÈÈÈØØØ®®®:::ŽŽŽlll¨¨¨–––¸¸¸888¶¶¶ÌÌÌ!þCreated with ajaxload.info!ù !ÿ NETSCAPE2.0,ÿ@€pH €bÁ$Ĩtx@$®W@e»å8>S©‚€-k¹\Œ'<\0Êf4Ä`𸜠/yXg{w Q o X h Ddƒ a†eTy›vkyBVevCp“y¡¶CyFp¿QpGpPÆCpHp¬Í«pIp’ÔpJÚÍØßÜeÒß ÖXÌÔϧÄßÈe½ÔÁp²X´ŒáÒ%䀪ia6½ÂŽÑ'_S$äjtÕEYÊ<Š´MÁƒzhŠò‡¢*AY š‚¥I8€Ø¸q‰’ÁJ6c½¬ˆÐN8/Àúf‰sîÙ !ù ,ÿ@€pH ÀP Ĩtx@$®WöŠ8L§ «'ŠïpÁ0gÝ ÆB hÀewã»Øßóf! Q mx[  [ Dbd jx••Biti££BV[tC­fžµC¹c¯ÀC¹gcÉD¹cªÑ¹cÙ¼¹áÙÞ[àÙcL×å cMÏåcNÇå’[O¾ÙfPba™•lB¨-N¥ª•ÆŒ!šÞtú "†Þ`QÄÈ$}`ÒГ“Ì™bJ,{Ô°q Gȓܠ‰Våä˜ .åxI²¤É:A!ù ,ÿ@€pH ÀP Ĩtx@$®WöŠ8L§ «'ŠïpÁ0gÝ ÆB hÀewã»ØßófusD mx[  [e iCbd j‡XT•jif^V[tCž[f›®CfFc¸Q‘[Gc¿DcHc¤Æ cIcŒÆBcJÓÓÑØ´ÞÞËÜßÞ—XÅØæ½Øî µc·¿îŪXXýòÐÁ!F¹JÓÏ—t‡4qòÀ†C ½Á’hQ£GÁ²xá!J@cPJ Áà‹8*±ìQÃ&9 !b2ƒÆX•”cºp£u$É’&Oè!ù ,ÿ@€pH ÀP Ĩtx@$®WöŠ8L§ «'ŠïpÁ0gÝ ÆB hÀewã»ØßófusD mx[  [e iCbd j‡XT•jif^V[tCž[f›®CfFc¸Q‘[Gc¿DcHc¤Æ cIcŒÆBcJÓÓÑØÖËÜÎ[MÄÜÈ[N½ÜÁXO¶ÓºcPªX¬¿°c³£®€WP¸ôFÓ— :TjH¡7X-¢u!ƒ† ^¡¥@ICbôÅ"C† ÃædJŒ ¸ ãeJ ÿ~Uc3#¸A¢„‚ Š© !ù ,ÿ@€pH ÀP Ĩtx@$®WöŠ8L§ «'ŠïpÁ0gÝ ÆB hÀewã»ØßófusD mx[  [e iCbd j‡XT•jif^V[tCž[f›®CfFc¸Q‘[Gc¿DcHc¤Æ cIcŒÆBcJÓÓÑØÖËÜÎ[MÄÜÈ[N½ÜÁXO¶ÓºcPªX¬¿°c³®¦cP¸”¥B¾tút%ÔB+HÔáG$]¡¥‰ C#èKˆ(GnÙ£†” Un¤Æ˜d¢ùõÁ‡”ùNC†Á…%MžÐ !ù ,ÿ@€pH ÀP Ĩtx@$®WöŠ8L§ «'ŠïpÁ0gÝ ÆB hÀewã»ØßófusD mx[  [e iCbd j‡XT•jif^V[tCž[f›®CfFc¸Q‘[Gc¿DcHc¤Æ cIcŒÆBcJÓÓÛÛPØÕ[ÜÛØÎ[äçÈ[bíØÁXí׿ºc ph×ÁÃ/XcfpÀ+ScP}`ÇM‹&Nž…âÄ6@‰5z„ï¥( B³RRƒARìÀi‰e63áÈyx‰¥4ƪø”… ›$J˜8ñö%!ù ,ÿ@€pH ÀP Ĩtx@$®WöŠ8L§ «'ŠïpÁ0gÝ ÆB hÀewãËåÍcusD   [e iBˆˆZjx[C››—jif^¨tC¢[²J¶Cf ²ÁD“[š¨ÊDc²ÒC c²PÚcÀâÊcŽâäc«ÒÜ[McæÔ¤cæÌXOf>I6Š•-&(Ã5f€€à ”à1dx%êO©mmFaYÔèQ$"-EY°çE2 I±çå•=jØÔ„#ÇV7/ÑH«"¨¡EmF(aâ$Ü— !ù ,ÿ@€pH|$0 ÀP ĨTøqp*X, Áå"Ó©“-o»]‚""; // delete the next two lines if you prefer plain decimal display for fractional EV values ev_label = ev_label.replace(/\.3/, " 1/3"); ev_label = ev_label.replace(/\.7/, " 2/3"); $(hdr_image.base_name + "_exp_text").innerHTML = ev_label; } function change_exp( hdr_image, exp_change ) { hdr_active_image = hdr_image; hdr_image.exposure = hdr_image.exposure + exp_change; // clamp to exposure limits if( hdr_image.exposure > hdr_image.f8_stops*8 ) { hdr_image.exposure = hdr_image.f8_stops*8; } else if( hdr_image.exposure < 0 ) { hdr_image.exposure = 0; } exp_cur = Math.floor(hdr_image.exposure / 8); exp_shar = exp_cur + 1; exp_blend = Math.round((hdr_image.exposure - exp_cur*8)*hdr_image.f_step_res); // blend each image accordingly var i; for( i = 0; i < hdr_image.basis; i++ ) { var img_obj = $(hdr_image.base_name+"_"+i); img_obj.set('src', hdr_image.image_dir + hdr_image.base_name + "_"+exp_cur+"_" + (i+1) + ".jpg"); img_obj.setStyle('opacity', cf[exp_blend][i]); } for( i = 0; i < hdr_image.shared_basis; i++ ) { var img_obj = $(hdr_image.base_name+"_"+(i+hdr_image.basis)); img_obj.set('src',hdr_image.image_dir + hdr_image.base_name + "_"+exp_shar+"_" + (i+1) + ".jpg"); img_obj.setStyle( 'opacity', cf[exp_blend][i+hdr_image.basis] ); } update_exp_text( hdr_image, hdr_image.exposure ); } // keyboartd events function hdr_onkeydown(e) { var hdr_image = hdr_active_image; var keynum; if( hdr_image == null ) return; var hdrnum = hdr_names.indexOf(hdr_image.base_name); if(window.event) { // IE keynum = window.event.keyCode; if( keynum == 189 ) hdr_slider[hdrnum].set(hdr_image.exposure*10 - 10); if( keynum == 187 ) hdr_slider[hdrnum].set(hdr_image.exposure*10 + 10); } else if(e.which) { // Netscape/Firefox/Opera keynum = e.which; if( keynum == 109 ) hdr_slider[hdrnum].set(hdr_image.exposure*10 - 10); if( keynum == 61 ) hdr_slider[hdrnum].set(hdr_image.exposure*10 + 10); } } // ---------------------------------------- // This must be called from within the HTML // ---------------------------------------- // - injects images into viewport // - binds slider to EV // - binds mousewheel to slider // Called once from each "hdr_viewer" div // inject images into HTML function insert_hdr_image( hdr_image ) { hdr_viewport = $(hdr_image.base_name+'_viewport'); hdr_viewer = hdr_viewport.getParent('div.hdr_viewer'); // preload images var preloading = new Array(); var k = 0; for( i = 0; i < hdr_image.f8_stops; i++ ) { for( j = 0; j < hdr_image.basis; j++ ) { preloading[k] = ""+hdr_image.image_dir + hdr_image.base_name + "_"+i+"_" + (j+1) + ".jpg"; k = k + 1; } } preloading[k] = ""+hdr_image.image_dir + hdr_image.base_name + "_"+hdr_image.f8_stops+"_1.jpg"; // for showing the AJAX spinning circle swap the next line with the block below new Asset.images(preloading); // var loadspinner = new Spinner(hdr_viewer).show('noFX'); // var myImages = new Asset.images(preloading, { // onComplete: function(){ // loadspinner.destroy(); // } // }); // enumerate all instances to remember the name hdrcount = hdrcount + 1; hdr_names[hdrcount] = ""+hdr_image.base_name; var injection = ""; for( i = 0; i < hdr_image.basis + hdr_image.shared_basis; i++ ) { injection += "\n"; } // also inject a hidden help viewer injection += "
" + "

HDR HTML Viewer

" + "v1.0 (C) 2008 Rafal Mantiuk | pfsTools
" + "v1.7 mod 2010 Christian Bloch | HDR Labs
" + "
  • Change exposure by sliding the dynamic range window left and right. Or just use the scroll wheel.
  • " + "
  • Press \"-\" and \"=\" keys to change exposure by 1 f-stop.
  • " + "
  • The green plot represents the HDR histogram. EV value is given in f-stops (log2 units) " + "relative to the inital exposure.
  • " + "
  • The exposure window / slider encompasses 8 f-stops.
  • " + "
"; hdr_viewport.set('html',injection); // link help viewer to help buttom var help_view = $(hdr_image.base_name+'_help_view').fade('hide');; $(hdr_image.base_name+'_help').addEvent('click', function() { if(help_view.getStyle('opacity') == 0) help_view.fade(0.85); else help_view.fade('out'); }); help_view.addEvent('click', function() { help_view.fade('out'); }); // initialize mootools slider hist_left = Math.round((-hdr_image.hist_start)*hdr_image.pix_per_fstop); var el = $(hdr_image.base_name+'_dr_ctrl'); el.style.left = Math.round(hist_left) + "px"; el.style.width = Math.round(hdr_image.hist_width-hist_left) + "px"; el.getElement('.knob').style.width = hdr_image.pix_per_fstop*8 + "px"; var EVrange = (hdr_image.hist_width-hist_left)/hdr_image.pix_per_fstop-7.7; var slider = new Slider(el, el.getElement('.knob'), { steps: EVrange*hdr_image.f_step_res, range: [0,EVrange*10], onChange: function(value) { change_exp( hdr_image, value/10 - hdr_image.exposure); } }).set(hdr_image.exposure*10); // clone this slider into an array for keyboard access hdr_slider[hdrcount] = slider; // link active hdr image to rollover on the current image hdr_viewer.addEvent('mouseenter', function() { hdr_active_image = hdr_image; // suppress accidental selection of individual images or text noselect = true; }); // link the mousewheel to the slider hdr_viewer.addEvent('mousewheel', function(e) { e.stop(); // prevent the mousewheel from scrolling the page. noselect = true; slider.set((hdr_image.exposure + e.wheel/2)*10); }); // restore outside text to be selectable hdr_viewer.addEvent('mouseleave', function() { noselect = false; }); } pfstools-2.2.0/src/hdrhtml/hdrhtml_hdrlabs_templ/hdrhtml_assets/slider-black.png0000664000701400070140000000051314105165614026741 0ustar rkm38rkm38‰PNG  IHDR(^Ý\ÝtEXtSoftwareAdobe ImageReadyqÉe<íIDATxÚì—Q ƒ0 †çö´c v›Gr$ÁÛÃ×,BÊ\m×´¶ÚAÿ[M>ø Ä/5W£€ ¨€Úø ô‚w=¿McK¼S:ʼôÌá·[ðEy(FšŒÜ#; Ñ‰ŽÄšˆ´Km¹EÚC€.9ƒÚ}€¿t–ôj·%:Kn´¯¥:K~i_Ø®Ü䯩¥><–b¨@1¸¯ëINx¶xÍ´”éÀ‰g%/j(¸¨aÏ¢–jOÜèÌu,¸´Çzuæ>· áÜ‚#Î-—öP‰túõŸDðßß Ù zTø“ŒkIEND®B`‚pfstools-2.2.0/src/hdrhtml/hdrhtml_hdrlabs_templ/hdrhtml_assets/mootools-1.2.4.js0000664000701400070140000026043314105165614026561 0ustar rkm38rkm38//MooTools, , My Object Oriented (JavaScript) Tools. Copyright (c) 2006-2009 Valerio Proietti, , MIT Style License. var MooTools={version:"1.2.4",build:"0d9113241a90b9cd5643b926795852a2026710d4"};var Native=function(k){k=k||{};var a=k.name;var i=k.legacy;var b=k.protect; var c=k.implement;var h=k.generics;var f=k.initialize;var g=k.afterImplement||function(){};var d=f||i;h=h!==false;d.constructor=Native;d.$family={name:"native"}; if(i&&f){d.prototype=i.prototype;}d.prototype.constructor=d;if(a){var e=a.toLowerCase();d.prototype.$family={name:e};Native.typize(d,e);}var j=function(n,l,o,m){if(!b||m||!n.prototype[l]){n.prototype[l]=o; }if(h){Native.genericize(n,l,b);}g.call(n,l,o);return n;};d.alias=function(n,l,p){if(typeof n=="string"){var o=this.prototype[n];if((n=o)){return j(this,l,n,p); }}for(var m in n){this.alias(m,n[m],l);}return this;};d.implement=function(m,l,o){if(typeof m=="string"){return j(this,m,l,o);}for(var n in m){j(this,n,m[n],l); }return this;};if(c){d.implement(c);}return d;};Native.genericize=function(b,c,a){if((!a||!b[c])&&typeof b.prototype[c]=="function"){b[c]=function(){var d=Array.prototype.slice.call(arguments); return b.prototype[c].apply(d.shift(),d);};}};Native.implement=function(d,c){for(var b=0,a=d.length;b-1:this.indexOf(a)>-1;},trim:function(){return this.replace(/^\s+|\s+$/g,"");},clean:function(){return this.replace(/\s+/g," ").trim(); },camelCase:function(){return this.replace(/-\D/g,function(a){return a.charAt(1).toUpperCase();});},hyphenate:function(){return this.replace(/[A-Z]/g,function(a){return("-"+a.charAt(0).toLowerCase()); });},capitalize:function(){return this.replace(/\b[a-z]/g,function(a){return a.toUpperCase();});},escapeRegExp:function(){return this.replace(/([-.*+?^${}()|[\]\/\\])/g,"\\$1"); },toInt:function(a){return parseInt(this,a||10);},toFloat:function(){return parseFloat(this);},hexToRgb:function(b){var a=this.match(/^#?(\w{1,2})(\w{1,2})(\w{1,2})$/); return(a)?a.slice(1).hexToRgb(b):null;},rgbToHex:function(b){var a=this.match(/\d{1,3}/g);return(a)?a.rgbToHex(b):null;},stripScripts:function(b){var a=""; var c=this.replace(/]*>([\s\S]*?)<\/script>/gi,function(){a+=arguments[1]+"\n";return"";});if(b===true){$exec(a);}else{if($type(b)=="function"){b(a,c); }}return c;},substitute:function(a,b){return this.replace(b||(/\\?\{([^{}]+)\}/g),function(d,c){if(d.charAt(0)=="\\"){return d.slice(1);}return(a[c]!=undefined)?a[c]:""; });}});Hash.implement({has:Object.prototype.hasOwnProperty,keyOf:function(b){for(var a in this){if(this.hasOwnProperty(a)&&this[a]===b){return a;}}return null; },hasValue:function(a){return(Hash.keyOf(this,a)!==null);},extend:function(a){Hash.each(a||{},function(c,b){Hash.set(this,b,c);},this);return this;},combine:function(a){Hash.each(a||{},function(c,b){Hash.include(this,b,c); },this);return this;},erase:function(a){if(this.hasOwnProperty(a)){delete this[a];}return this;},get:function(a){return(this.hasOwnProperty(a))?this[a]:null; },set:function(a,b){if(!this[a]||this.hasOwnProperty(a)){this[a]=b;}return this;},empty:function(){Hash.each(this,function(b,a){delete this[a];},this); return this;},include:function(a,b){if(this[a]==undefined){this[a]=b;}return this;},map:function(b,c){var a=new Hash;Hash.each(this,function(e,d){a.set(d,b.call(c,e,d,this)); },this);return a;},filter:function(b,c){var a=new Hash;Hash.each(this,function(e,d){if(b.call(c,e,d,this)){a.set(d,e);}},this);return a;},every:function(b,c){for(var a in this){if(this.hasOwnProperty(a)&&!b.call(c,this[a],a)){return false; }}return true;},some:function(b,c){for(var a in this){if(this.hasOwnProperty(a)&&b.call(c,this[a],a)){return true;}}return false;},getKeys:function(){var a=[]; Hash.each(this,function(c,b){a.push(b);});return a;},getValues:function(){var a=[];Hash.each(this,function(b){a.push(b);});return a;},toQueryString:function(a){var b=[]; Hash.each(this,function(f,e){if(a){e=a+"["+e+"]";}var d;switch($type(f)){case"object":d=Hash.toQueryString(f,e);break;case"array":var c={};f.each(function(h,g){c[g]=h; });d=Hash.toQueryString(c,e);break;default:d=e+"="+encodeURIComponent(f);}if(f!=undefined){b.push(d);}});return b.join("&");}});Hash.alias({keyOf:"indexOf",hasValue:"contains"}); var Event=new Native({name:"Event",initialize:function(a,f){f=f||window;var k=f.document;a=a||f.event;if(a.$extended){return a;}this.$extended=true;var j=a.type; var g=a.target||a.srcElement;while(g&&g.nodeType==3){g=g.parentNode;}if(j.test(/key/)){var b=a.which||a.keyCode;var m=Event.Keys.keyOf(b);if(j=="keydown"){var d=b-111; if(d>0&&d<13){m="f"+d;}}m=m||String.fromCharCode(b).toLowerCase();}else{if(j.match(/(click|mouse|menu)/i)){k=(!k.compatMode||k.compatMode=="CSS1Compat")?k.html:k.body; var i={x:a.pageX||a.clientX+k.scrollLeft,y:a.pageY||a.clientY+k.scrollTop};var c={x:(a.pageX)?a.pageX-f.pageXOffset:a.clientX,y:(a.pageY)?a.pageY-f.pageYOffset:a.clientY}; if(j.match(/DOMMouseScroll|mousewheel/)){var h=(a.wheelDelta)?a.wheelDelta/120:-(a.detail||0)/3;}var e=(a.which==3)||(a.button==2);var l=null;if(j.match(/over|out/)){switch(j){case"mouseover":l=a.relatedTarget||a.fromElement; break;case"mouseout":l=a.relatedTarget||a.toElement;}if(!(function(){while(l&&l.nodeType==3){l=l.parentNode;}return true;}).create({attempt:Browser.Engine.gecko})()){l=false; }}}}return $extend(this,{event:a,type:j,page:i,client:c,rightClick:e,wheel:h,relatedTarget:l,target:g,code:b,key:m,shift:a.shiftKey,control:a.ctrlKey,alt:a.altKey,meta:a.metaKey}); }});Event.Keys=new Hash({enter:13,up:38,down:40,left:37,right:39,esc:27,space:32,backspace:8,tab:9,"delete":46});Event.implement({stop:function(){return this.stopPropagation().preventDefault(); },stopPropagation:function(){if(this.event.stopPropagation){this.event.stopPropagation();}else{this.event.cancelBubble=true;}return this;},preventDefault:function(){if(this.event.preventDefault){this.event.preventDefault(); }else{this.event.returnValue=false;}return this;}});function Class(b){if(b instanceof Function){b={initialize:b};}var a=function(){Object.reset(this);if(a._prototyping){return this; }this._current=$empty;var c=(this.initialize)?this.initialize.apply(this,arguments):this;delete this._current;delete this.caller;return c;}.extend(this); a.implement(b);a.constructor=Class;a.prototype.constructor=a;return a;}Function.prototype.protect=function(){this._protected=true;return this;};Object.reset=function(a,c){if(c==null){for(var e in a){Object.reset(a,e); }return a;}delete a[c];switch($type(a[c])){case"object":var d=function(){};d.prototype=a[c];var b=new d;a[c]=Object.reset(b);break;case"array":a[c]=$unlink(a[c]); break;}return a;};new Native({name:"Class",initialize:Class}).extend({instantiate:function(b){b._prototyping=true;var a=new b;delete b._prototyping;return a; },wrap:function(a,b,c){if(c._origin){c=c._origin;}return function(){if(c._protected&&this._current==null){throw new Error('The method "'+b+'" cannot be called.'); }var e=this.caller,f=this._current;this.caller=f;this._current=arguments.callee;var d=c.apply(this,arguments);this._current=f;this.caller=e;return d;}.extend({_owner:a,_origin:c,_name:b}); }});Class.implement({implement:function(a,d){if($type(a)=="object"){for(var e in a){this.implement(e,a[e]);}return this;}var f=Class.Mutators[a];if(f){d=f.call(this,d); if(d==null){return this;}}var c=this.prototype;switch($type(d)){case"function":if(d._hidden){return this;}c[a]=Class.wrap(this,a,d);break;case"object":var b=c[a]; if($type(b)=="object"){$mixin(b,d);}else{c[a]=$unlink(d);}break;case"array":c[a]=$unlink(d);break;default:c[a]=d;}return this;}});Class.Mutators={Extends:function(a){this.parent=a; this.prototype=Class.instantiate(a);this.implement("parent",function(){var b=this.caller._name,c=this.caller._owner.parent.prototype[b];if(!c){throw new Error('The method "'+b+'" has no parent.'); }return c.apply(this,arguments);}.protect());},Implements:function(a){$splat(a).each(function(b){if(b instanceof Function){b=Class.instantiate(b);}this.implement(b); },this);}};var Chain=new Class({$chain:[],chain:function(){this.$chain.extend(Array.flatten(arguments));return this;},callChain:function(){return(this.$chain.length)?this.$chain.shift().apply(this,arguments):false; },clearChain:function(){this.$chain.empty();return this;}});var Events=new Class({$events:{},addEvent:function(c,b,a){c=Events.removeOn(c);if(b!=$empty){this.$events[c]=this.$events[c]||[]; this.$events[c].include(b);if(a){b.internal=true;}}return this;},addEvents:function(a){for(var b in a){this.addEvent(b,a[b]);}return this;},fireEvent:function(c,b,a){c=Events.removeOn(c); if(!this.$events||!this.$events[c]){return this;}this.$events[c].each(function(d){d.create({bind:this,delay:a,"arguments":b})();},this);return this;},removeEvent:function(b,a){b=Events.removeOn(b); if(!this.$events[b]){return this;}if(!a.internal){this.$events[b].erase(a);}return this;},removeEvents:function(c){var d;if($type(c)=="object"){for(d in c){this.removeEvent(d,c[d]); }return this;}if(c){c=Events.removeOn(c);}for(d in this.$events){if(c&&c!=d){continue;}var b=this.$events[d];for(var a=b.length;a--;a){this.removeEvent(d,b[a]); }}return this;}});Events.removeOn=function(a){return a.replace(/^on([A-Z])/,function(b,c){return c.toLowerCase();});};var Options=new Class({setOptions:function(){this.options=$merge.run([this.options].extend(arguments)); if(!this.addEvent){return this;}for(var a in this.options){if($type(this.options[a])!="function"||!(/^on[A-Z]/).test(a)){continue;}this.addEvent(a,this.options[a]); delete this.options[a];}return this;}});var Element=new Native({name:"Element",legacy:window.Element,initialize:function(a,b){var c=Element.Constructors.get(a); if(c){return c(b);}if(typeof a=="string"){return document.newElement(a,b);}return document.id(a).set(b);},afterImplement:function(a,b){Element.Prototype[a]=b; if(Array[a]){return;}Elements.implement(a,function(){var c=[],g=true;for(var e=0,d=this.length;e";}return document.id(this.createElement(a)).set(b);},newTextNode:function(a){return this.createTextNode(a); },getDocument:function(){return this;},getWindow:function(){return this.window;},id:(function(){var a={string:function(d,c,b){d=b.getElementById(d);return(d)?a.element(d,c):null; },element:function(b,e){$uid(b);if(!e&&!b.$family&&!(/^object|embed$/i).test(b.tagName)){var c=Element.Prototype;for(var d in c){b[d]=c[d];}}return b;},object:function(c,d,b){if(c.toElement){return a.element(c.toElement(b),d); }return null;}};a.textnode=a.whitespace=a.window=a.document=$arguments(0);return function(c,e,d){if(c&&c.$family&&c.uid){return c;}var b=$type(c);return(a[b])?a[b](c,e,d||document):null; };})()});if(window.$==null){Window.implement({$:function(a,b){return document.id(a,b,this.document);}});}Window.implement({$$:function(a){if(arguments.length==1&&typeof a=="string"){return this.document.getElements(a); }var f=[];var c=Array.flatten(arguments);for(var d=0,b=c.length;d1);a.each(function(e){var f=this.getElementsByTagName(e.trim());(b)?c.extend(f):c=f; },this);return new Elements(c,{ddup:b,cash:!d});}});(function(){var h={},f={};var i={input:"checked",option:"selected",textarea:(Browser.Engine.webkit&&Browser.Engine.version<420)?"innerHTML":"value"}; var c=function(l){return(f[l]||(f[l]={}));};var g=function(n,l){if(!n){return;}var m=n.uid;if(Browser.Engine.trident){if(n.clearAttributes){var q=l&&n.cloneNode(false); n.clearAttributes();if(q){n.mergeAttributes(q);}}else{if(n.removeEvents){n.removeEvents();}}if((/object/i).test(n.tagName)){for(var o in n){if(typeof n[o]=="function"){n[o]=$empty; }}Element.dispose(n);}}if(!m){return;}h[m]=f[m]=null;};var d=function(){Hash.each(h,g);if(Browser.Engine.trident){$A(document.getElementsByTagName("object")).each(g); }if(window.CollectGarbage){CollectGarbage();}h=f=null;};var j=function(n,l,s,m,p,r){var o=n[s||l];var q=[];while(o){if(o.nodeType==1&&(!m||Element.match(o,m))){if(!p){return document.id(o,r); }q.push(o);}o=o[l];}return(p)?new Elements(q,{ddup:false,cash:!r}):null;};var e={html:"innerHTML","class":"className","for":"htmlFor",defaultValue:"defaultValue",text:(Browser.Engine.trident||(Browser.Engine.webkit&&Browser.Engine.version<420))?"innerText":"textContent"}; var b=["compact","nowrap","ismap","declare","noshade","checked","disabled","readonly","multiple","selected","noresize","defer"];var k=["value","type","defaultValue","accessKey","cellPadding","cellSpacing","colSpan","frameBorder","maxLength","readOnly","rowSpan","tabIndex","useMap"]; b=b.associate(b);Hash.extend(e,b);Hash.extend(e,k.associate(k.map(String.toLowerCase)));var a={before:function(m,l){if(l.parentNode){l.parentNode.insertBefore(m,l); }},after:function(m,l){if(!l.parentNode){return;}var n=l.nextSibling;(n)?l.parentNode.insertBefore(m,n):l.parentNode.appendChild(m);},bottom:function(m,l){l.appendChild(m); },top:function(m,l){var n=l.firstChild;(n)?l.insertBefore(m,n):l.appendChild(m);}};a.inside=a.bottom;Hash.each(a,function(l,m){m=m.capitalize();Element.implement("inject"+m,function(n){l(this,document.id(n,true)); return this;});Element.implement("grab"+m,function(n){l(document.id(n,true),this);return this;});});Element.implement({set:function(o,m){switch($type(o)){case"object":for(var n in o){this.set(n,o[n]); }break;case"string":var l=Element.Properties.get(o);(l&&l.set)?l.set.apply(this,Array.slice(arguments,1)):this.setProperty(o,m);}return this;},get:function(m){var l=Element.Properties.get(m); return(l&&l.get)?l.get.apply(this,Array.slice(arguments,1)):this.getProperty(m);},erase:function(m){var l=Element.Properties.get(m);(l&&l.erase)?l.erase.apply(this):this.removeProperty(m); return this;},setProperty:function(m,n){var l=e[m];if(n==undefined){return this.removeProperty(m);}if(l&&b[m]){n=!!n;}(l)?this[l]=n:this.setAttribute(m,""+n); return this;},setProperties:function(l){for(var m in l){this.setProperty(m,l[m]);}return this;},getProperty:function(m){var l=e[m];var n=(l)?this[l]:this.getAttribute(m,2); return(b[m])?!!n:(l)?n:n||null;},getProperties:function(){var l=$A(arguments);return l.map(this.getProperty,this).associate(l);},removeProperty:function(m){var l=e[m]; (l)?this[l]=(l&&b[m])?false:"":this.removeAttribute(m);return this;},removeProperties:function(){Array.each(arguments,this.removeProperty,this);return this; },hasClass:function(l){return this.className.contains(l," ");},addClass:function(l){if(!this.hasClass(l)){this.className=(this.className+" "+l).clean(); }return this;},removeClass:function(l){this.className=this.className.replace(new RegExp("(^|\\s)"+l+"(?:\\s|$)"),"$1");return this;},toggleClass:function(l){return this.hasClass(l)?this.removeClass(l):this.addClass(l); },adopt:function(){Array.flatten(arguments).each(function(l){l=document.id(l,true);if(l){this.appendChild(l);}},this);return this;},appendText:function(m,l){return this.grab(this.getDocument().newTextNode(m),l); },grab:function(m,l){a[l||"bottom"](document.id(m,true),this);return this;},inject:function(m,l){a[l||"bottom"](this,document.id(m,true));return this;},replaces:function(l){l=document.id(l,true); l.parentNode.replaceChild(this,l);return this;},wraps:function(m,l){m=document.id(m,true);return this.replaces(m).grab(m,l);},getPrevious:function(l,m){return j(this,"previousSibling",null,l,false,m); },getAllPrevious:function(l,m){return j(this,"previousSibling",null,l,true,m);},getNext:function(l,m){return j(this,"nextSibling",null,l,false,m);},getAllNext:function(l,m){return j(this,"nextSibling",null,l,true,m); },getFirst:function(l,m){return j(this,"nextSibling","firstChild",l,false,m);},getLast:function(l,m){return j(this,"previousSibling","lastChild",l,false,m); },getParent:function(l,m){return j(this,"parentNode",null,l,false,m);},getParents:function(l,m){return j(this,"parentNode",null,l,true,m);},getSiblings:function(l,m){return this.getParent().getChildren(l,m).erase(this); },getChildren:function(l,m){return j(this,"nextSibling","firstChild",l,true,m);},getWindow:function(){return this.ownerDocument.window;},getDocument:function(){return this.ownerDocument; },getElementById:function(o,n){var m=this.ownerDocument.getElementById(o);if(!m){return null;}for(var l=m.parentNode;l!=this;l=l.parentNode){if(!l){return null; }}return document.id(m,n);},getSelected:function(){return new Elements($A(this.options).filter(function(l){return l.selected;}));},getComputedStyle:function(m){if(this.currentStyle){return this.currentStyle[m.camelCase()]; }var l=this.getDocument().defaultView.getComputedStyle(this,null);return(l)?l.getPropertyValue([m.hyphenate()]):null;},toQueryString:function(){var l=[]; this.getElements("input, select, textarea",true).each(function(m){if(!m.name||m.disabled||m.type=="submit"||m.type=="reset"||m.type=="file"){return;}var n=(m.tagName.toLowerCase()=="select")?Element.getSelected(m).map(function(o){return o.value; }):((m.type=="radio"||m.type=="checkbox")&&!m.checked)?null:m.value;$splat(n).each(function(o){if(typeof o!="undefined"){l.push(m.name+"="+encodeURIComponent(o)); }});});return l.join("&");},clone:function(o,l){o=o!==false;var r=this.cloneNode(o);var n=function(v,u){if(!l){v.removeAttribute("id");}if(Browser.Engine.trident){v.clearAttributes(); v.mergeAttributes(u);v.removeAttribute("uid");if(v.options){var w=v.options,s=u.options;for(var t=w.length;t--;){w[t].selected=s[t].selected;}}}var x=i[u.tagName.toLowerCase()]; if(x&&u[x]){v[x]=u[x];}};if(o){var p=r.getElementsByTagName("*"),q=this.getElementsByTagName("*");for(var m=p.length;m--;){n(p[m],q[m]);}}n(r,this);return document.id(r); },destroy:function(){Element.empty(this);Element.dispose(this);g(this,true);return null;},empty:function(){$A(this.childNodes).each(function(l){Element.destroy(l); });return this;},dispose:function(){return(this.parentNode)?this.parentNode.removeChild(this):this;},hasChild:function(l){l=document.id(l,true);if(!l){return false; }if(Browser.Engine.webkit&&Browser.Engine.version<420){return $A(this.getElementsByTagName(l.tagName)).contains(l);}return(this.contains)?(this!=l&&this.contains(l)):!!(this.compareDocumentPosition(l)&16); },match:function(l){return(!l||(l==this)||(Element.get(this,"tag")==l));}});Native.implement([Element,Window,Document],{addListener:function(o,n){if(o=="unload"){var l=n,m=this; n=function(){m.removeListener("unload",n);l();};}else{h[this.uid]=this;}if(this.addEventListener){this.addEventListener(o,n,false);}else{this.attachEvent("on"+o,n); }return this;},removeListener:function(m,l){if(this.removeEventListener){this.removeEventListener(m,l,false);}else{this.detachEvent("on"+m,l);}return this; },retrieve:function(m,l){var o=c(this.uid),n=o[m];if(l!=undefined&&n==undefined){n=o[m]=l;}return $pick(n);},store:function(m,l){var n=c(this.uid);n[m]=l; return this;},eliminate:function(l){var m=c(this.uid);delete m[l];return this;}});window.addListener("unload",d);})();Element.Properties=new Hash;Element.Properties.style={set:function(a){this.style.cssText=a; },get:function(){return this.style.cssText;},erase:function(){this.style.cssText="";}};Element.Properties.tag={get:function(){return this.tagName.toLowerCase(); }};Element.Properties.html=(function(){var c=document.createElement("div");var a={table:[1,"","
"],select:[1,""],tbody:[2,"","
"],tr:[3,"","
"]}; a.thead=a.tfoot=a.tbody;var b={set:function(){var e=Array.flatten(arguments).join("");var f=Browser.Engine.trident&&a[this.get("tag")];if(f){var g=c;g.innerHTML=f[1]+e+f[2]; for(var d=f[0];d--;){g=g.firstChild;}this.empty().adopt(g.childNodes);}else{this.innerHTML=e;}}};b.erase=b.set;return b;})();if(Browser.Engine.webkit&&Browser.Engine.version<420){Element.Properties.text={get:function(){if(this.innerText){return this.innerText; }var a=this.ownerDocument.newElement("div",{html:this.innerHTML}).inject(this.ownerDocument.body);var b=a.innerText;a.destroy();return b;}};}Element.Properties.events={set:function(a){this.addEvents(a); }};Native.implement([Element,Window,Document],{addEvent:function(e,g){var h=this.retrieve("events",{});h[e]=h[e]||{keys:[],values:[]};if(h[e].keys.contains(g)){return this; }h[e].keys.push(g);var f=e,a=Element.Events.get(e),c=g,i=this;if(a){if(a.onAdd){a.onAdd.call(this,g);}if(a.condition){c=function(j){if(a.condition.call(this,j)){return g.call(this,j); }return true;};}f=a.base||f;}var d=function(){return g.call(i);};var b=Element.NativeEvents[f];if(b){if(b==2){d=function(j){j=new Event(j,i.getWindow()); if(c.call(i,j)===false){j.stop();}};}this.addListener(f,d);}h[e].values.push(d);return this;},removeEvent:function(c,b){var a=this.retrieve("events");if(!a||!a[c]){return this; }var f=a[c].keys.indexOf(b);if(f==-1){return this;}a[c].keys.splice(f,1);var e=a[c].values.splice(f,1)[0];var d=Element.Events.get(c);if(d){if(d.onRemove){d.onRemove.call(this,b); }c=d.base||c;}return(Element.NativeEvents[c])?this.removeListener(c,e):this;},addEvents:function(a){for(var b in a){this.addEvent(b,a[b]);}return this; },removeEvents:function(a){var c;if($type(a)=="object"){for(c in a){this.removeEvent(c,a[c]);}return this;}var b=this.retrieve("events");if(!b){return this; }if(!a){for(c in b){this.removeEvents(c);}this.eliminate("events");}else{if(b[a]){while(b[a].keys[0]){this.removeEvent(a,b[a].keys[0]);}b[a]=null;}}return this; },fireEvent:function(d,b,a){var c=this.retrieve("events");if(!c||!c[d]){return this;}c[d].keys.each(function(e){e.create({bind:this,delay:a,"arguments":b})(); },this);return this;},cloneEvents:function(d,a){d=document.id(d);var c=d.retrieve("events");if(!c){return this;}if(!a){for(var b in c){this.cloneEvents(d,b); }}else{if(c[a]){c[a].keys.each(function(e){this.addEvent(a,e);},this);}}return this;}});Element.NativeEvents={click:2,dblclick:2,mouseup:2,mousedown:2,contextmenu:2,mousewheel:2,DOMMouseScroll:2,mouseover:2,mouseout:2,mousemove:2,selectstart:2,selectend:2,keydown:2,keypress:2,keyup:2,focus:2,blur:2,change:2,reset:2,select:2,submit:2,load:1,unload:1,beforeunload:2,resize:1,move:1,DOMContentLoaded:1,readystatechange:1,error:1,abort:1,scroll:1}; (function(){var a=function(b){var c=b.relatedTarget;if(c==undefined){return true;}if(c===false){return false;}return($type(this)!="document"&&c!=this&&c.prefix!="xul"&&!this.hasChild(c)); };Element.Events=new Hash({mouseenter:{base:"mouseover",condition:a},mouseleave:{base:"mouseout",condition:a},mousewheel:{base:(Browser.Engine.gecko)?"DOMMouseScroll":"mousewheel"}}); })();Element.Properties.styles={set:function(a){this.setStyles(a);}};Element.Properties.opacity={set:function(a,b){if(!b){if(a==0){if(this.style.visibility!="hidden"){this.style.visibility="hidden"; }}else{if(this.style.visibility!="visible"){this.style.visibility="visible";}}}if(!this.currentStyle||!this.currentStyle.hasLayout){this.style.zoom=1;}if(Browser.Engine.trident){this.style.filter=(a==1)?"":"alpha(opacity="+a*100+")"; }this.style.opacity=a;this.store("opacity",a);},get:function(){return this.retrieve("opacity",1);}};Element.implement({setOpacity:function(a){return this.set("opacity",a,true); },getOpacity:function(){return this.get("opacity");},setStyle:function(b,a){switch(b){case"opacity":return this.set("opacity",parseFloat(a));case"float":b=(Browser.Engine.trident)?"styleFloat":"cssFloat"; }b=b.camelCase();if($type(a)!="string"){var c=(Element.Styles.get(b)||"@").split(" ");a=$splat(a).map(function(e,d){if(!c[d]){return"";}return($type(e)=="number")?c[d].replace("@",Math.round(e)):e; }).join(" ");}else{if(a==String(Number(a))){a=Math.round(a);}}this.style[b]=a;return this;},getStyle:function(g){switch(g){case"opacity":return this.get("opacity"); case"float":g=(Browser.Engine.trident)?"styleFloat":"cssFloat";}g=g.camelCase();var a=this.style[g];if(!$chk(a)){a=[];for(var f in Element.ShortStyles){if(g!=f){continue; }for(var e in Element.ShortStyles[f]){a.push(this.getStyle(e));}return a.join(" ");}a=this.getComputedStyle(g);}if(a){a=String(a);var c=a.match(/rgba?\([\d\s,]+\)/); if(c){a=a.replace(c[0],c[0].rgbToHex());}}if(Browser.Engine.presto||(Browser.Engine.trident&&!$chk(parseInt(a,10)))){if(g.test(/^(height|width)$/)){var b=(g=="width")?["left","right"]:["top","bottom"],d=0; b.each(function(h){d+=this.getStyle("border-"+h+"-width").toInt()+this.getStyle("padding-"+h).toInt();},this);return this["offset"+g.capitalize()]-d+"px"; }if((Browser.Engine.presto)&&String(a).test("px")){return a;}if(g.test(/(border(.+)Width|margin|padding)/)){return"0px";}}return a;},setStyles:function(b){for(var a in b){this.setStyle(a,b[a]); }return this;},getStyles:function(){var a={};Array.flatten(arguments).each(function(b){a[b]=this.getStyle(b);},this);return a;}});Element.Styles=new Hash({left:"@px",top:"@px",bottom:"@px",right:"@px",width:"@px",height:"@px",maxWidth:"@px",maxHeight:"@px",minWidth:"@px",minHeight:"@px",backgroundColor:"rgb(@, @, @)",backgroundPosition:"@px @px",color:"rgb(@, @, @)",fontSize:"@px",letterSpacing:"@px",lineHeight:"@px",clip:"rect(@px @px @px @px)",margin:"@px @px @px @px",padding:"@px @px @px @px",border:"@px @ rgb(@, @, @) @px @ rgb(@, @, @) @px @ rgb(@, @, @)",borderWidth:"@px @px @px @px",borderStyle:"@ @ @ @",borderColor:"rgb(@, @, @) rgb(@, @, @) rgb(@, @, @) rgb(@, @, @)",zIndex:"@",zoom:"@",fontWeight:"@",textIndent:"@px",opacity:"@"}); Element.ShortStyles={margin:{},padding:{},border:{},borderWidth:{},borderStyle:{},borderColor:{}};["Top","Right","Bottom","Left"].each(function(g){var f=Element.ShortStyles; var b=Element.Styles;["margin","padding"].each(function(h){var i=h+g;f[h][i]=b[i]="@px";});var e="border"+g;f.border[e]=b[e]="@px @ rgb(@, @, @)";var d=e+"Width",a=e+"Style",c=e+"Color"; f[e]={};f.borderWidth[d]=f[e][d]=b[d]="@px";f.borderStyle[a]=f[e][a]=b[a]="@";f.borderColor[c]=f[e][c]=b[c]="rgb(@, @, @)";});(function(){Element.implement({scrollTo:function(h,i){if(b(this)){this.getWindow().scrollTo(h,i); }else{this.scrollLeft=h;this.scrollTop=i;}return this;},getSize:function(){if(b(this)){return this.getWindow().getSize();}return{x:this.offsetWidth,y:this.offsetHeight}; },getScrollSize:function(){if(b(this)){return this.getWindow().getScrollSize();}return{x:this.scrollWidth,y:this.scrollHeight};},getScroll:function(){if(b(this)){return this.getWindow().getScroll(); }return{x:this.scrollLeft,y:this.scrollTop};},getScrolls:function(){var i=this,h={x:0,y:0};while(i&&!b(i)){h.x+=i.scrollLeft;h.y+=i.scrollTop;i=i.parentNode; }return h;},getOffsetParent:function(){var h=this;if(b(h)){return null;}if(!Browser.Engine.trident){return h.offsetParent;}while((h=h.parentNode)&&!b(h)){if(d(h,"position")!="static"){return h; }}return null;},getOffsets:function(){if(this.getBoundingClientRect){var j=this.getBoundingClientRect(),m=document.id(this.getDocument().documentElement),p=m.getScroll(),k=this.getScrolls(),i=this.getScroll(),h=(d(this,"position")=="fixed"); return{x:j.left.toInt()+k.x-i.x+((h)?0:p.x)-m.clientLeft,y:j.top.toInt()+k.y-i.y+((h)?0:p.y)-m.clientTop};}var l=this,n={x:0,y:0};if(b(this)){return n; }while(l&&!b(l)){n.x+=l.offsetLeft;n.y+=l.offsetTop;if(Browser.Engine.gecko){if(!f(l)){n.x+=c(l);n.y+=g(l);}var o=l.parentNode;if(o&&d(o,"overflow")!="visible"){n.x+=c(o); n.y+=g(o);}}else{if(l!=this&&Browser.Engine.webkit){n.x+=c(l);n.y+=g(l);}}l=l.offsetParent;}if(Browser.Engine.gecko&&!f(this)){n.x-=c(this);n.y-=g(this); }return n;},getPosition:function(k){if(b(this)){return{x:0,y:0};}var l=this.getOffsets(),i=this.getScrolls();var h={x:l.x-i.x,y:l.y-i.y};var j=(k&&(k=document.id(k)))?k.getPosition():{x:0,y:0}; return{x:h.x-j.x,y:h.y-j.y};},getCoordinates:function(j){if(b(this)){return this.getWindow().getCoordinates();}var h=this.getPosition(j),i=this.getSize(); var k={left:h.x,top:h.y,width:i.x,height:i.y};k.right=k.left+k.width;k.bottom=k.top+k.height;return k;},computePosition:function(h){return{left:h.x-e(this,"margin-left"),top:h.y-e(this,"margin-top")}; },setPosition:function(h){return this.setStyles(this.computePosition(h));}});Native.implement([Document,Window],{getSize:function(){if(Browser.Engine.presto||Browser.Engine.webkit){var i=this.getWindow(); return{x:i.innerWidth,y:i.innerHeight};}var h=a(this);return{x:h.clientWidth,y:h.clientHeight};},getScroll:function(){var i=this.getWindow(),h=a(this); return{x:i.pageXOffset||h.scrollLeft,y:i.pageYOffset||h.scrollTop};},getScrollSize:function(){var i=a(this),h=this.getSize();return{x:Math.max(i.scrollWidth,h.x),y:Math.max(i.scrollHeight,h.y)}; },getPosition:function(){return{x:0,y:0};},getCoordinates:function(){var h=this.getSize();return{top:0,left:0,bottom:h.y,right:h.x,height:h.y,width:h.x}; }});var d=Element.getComputedStyle;function e(h,i){return d(h,i).toInt()||0;}function f(h){return d(h,"-moz-box-sizing")=="border-box";}function g(h){return e(h,"border-top-width"); }function c(h){return e(h,"border-left-width");}function b(h){return(/^(?:body|html)$/i).test(h.tagName);}function a(h){var i=h.getDocument();return(!i.compatMode||i.compatMode=="CSS1Compat")?i.html:i.body; }})();Element.alias("setPosition","position");Native.implement([Window,Document,Element],{getHeight:function(){return this.getSize().y;},getWidth:function(){return this.getSize().x; },getScrollTop:function(){return this.getScroll().y;},getScrollLeft:function(){return this.getScroll().x;},getScrollHeight:function(){return this.getScrollSize().y; },getScrollWidth:function(){return this.getScrollSize().x;},getTop:function(){return this.getPosition().y;},getLeft:function(){return this.getPosition().x; }});Native.implement([Document,Element],{getElements:function(h,g){h=h.split(",");var c,e={};for(var d=0,b=h.length;d1),cash:!g});}});Element.implement({match:function(b){if(!b||(b==this)){return true; }var d=Selectors.Utils.parseTagAndID(b);var a=d[0],e=d[1];if(!Selectors.Filters.byID(this,e)||!Selectors.Filters.byTag(this,a)){return false;}var c=Selectors.Utils.parseSelector(b); return(c)?Selectors.Utils.filter(this,c,{}):true;}});var Selectors={Cache:{nth:{},parsed:{}}};Selectors.RegExps={id:(/#([\w-]+)/),tag:(/^(\w+|\*)/),quick:(/^(\w+|\*)$/),splitter:(/\s*([+>~\s])\s*([a-zA-Z#.*:\[])/g),combined:(/\.([\w-]+)|\[(\w+)(?:([!*^$~|]?=)(["']?)([^\4]*?)\4)?\]|:([\w-]+)(?:\(["']?(.*?)?["']?\)|$)/g)}; Selectors.Utils={chk:function(b,c){if(!c){return true;}var a=$uid(b);if(!c[a]){return c[a]=true;}return false;},parseNthArgument:function(h){if(Selectors.Cache.nth[h]){return Selectors.Cache.nth[h]; }var e=h.match(/^([+-]?\d*)?([a-z]+)?([+-]?\d*)?$/);if(!e){return false;}var g=parseInt(e[1],10);var d=(g||g===0)?g:1;var f=e[2]||false;var c=parseInt(e[3],10)||0; if(d!=0){c--;while(c<1){c+=d;}while(c>=d){c-=d;}}else{d=c;f="index";}switch(f){case"n":e={a:d,b:c,special:"n"};break;case"odd":e={a:2,b:0,special:"n"}; break;case"even":e={a:2,b:1,special:"n"};break;case"first":e={a:0,special:"index"};break;case"last":e={special:"last-child"};break;case"only":e={special:"only-child"}; break;default:e={a:(d-1),special:"index"};}return Selectors.Cache.nth[h]=e;},parseSelector:function(e){if(Selectors.Cache.parsed[e]){return Selectors.Cache.parsed[e]; }var d,h={classes:[],pseudos:[],attributes:[]};while((d=Selectors.RegExps.combined.exec(e))){var i=d[1],g=d[2],f=d[3],b=d[5],c=d[6],j=d[7];if(i){h.classes.push(i); }else{if(c){var a=Selectors.Pseudo.get(c);if(a){h.pseudos.push({parser:a,argument:j});}else{h.attributes.push({name:c,operator:"=",value:j});}}else{if(g){h.attributes.push({name:g,operator:f,value:b}); }}}}if(!h.classes.length){delete h.classes;}if(!h.attributes.length){delete h.attributes;}if(!h.pseudos.length){delete h.pseudos;}if(!h.classes&&!h.attributes&&!h.pseudos){h=null; }return Selectors.Cache.parsed[e]=h;},parseTagAndID:function(b){var a=b.match(Selectors.RegExps.tag);var c=b.match(Selectors.RegExps.id);return[(a)?a[1]:"*",(c)?c[1]:false]; },filter:function(f,c,e){var d;if(c.classes){for(d=c.classes.length;d--;d){var g=c.classes[d];if(!Selectors.Filters.byClass(f,g)){return false;}}}if(c.attributes){for(d=c.attributes.length; d--;d){var b=c.attributes[d];if(!Selectors.Filters.byAttribute(f,b.name,b.operator,b.value)){return false;}}}if(c.pseudos){for(d=c.pseudos.length;d--;d){var a=c.pseudos[d]; if(!Selectors.Filters.byPseudo(f,a.parser,a.argument,e)){return false;}}}return true;},getByTagAndID:function(b,a,d){if(d){var c=(b.getElementById)?b.getElementById(d,true):Element.getElementById(b,d,true); return(c&&Selectors.Filters.byTag(c,a))?[c]:[];}else{return b.getElementsByTagName(a);}},search:function(o,h,t){var b=[];var c=h.trim().replace(Selectors.RegExps.splitter,function(k,j,i){b.push(j); return":)"+i;}).split(":)");var p,e,A;for(var z=0,v=c.length;z":function(h,g,j,a,f){var c=Selectors.Utils.getByTagAndID(g,j,a);for(var e=0,d=c.length;ea){return false;}}return(c==a);},even:function(b,a){return Selectors.Pseudo["nth-child"].call(this,"2n+1",a); },odd:function(b,a){return Selectors.Pseudo["nth-child"].call(this,"2n",a);},selected:function(){return this.selected;},enabled:function(){return(this.disabled===false); }});Element.Events.domready={onAdd:function(a){if(Browser.loaded){a.call(this);}}};(function(){var b=function(){if(Browser.loaded){return;}Browser.loaded=true; window.fireEvent("domready");document.fireEvent("domready");};window.addEvent("load",b);if(Browser.Engine.trident){var a=document.createElement("div"); (function(){($try(function(){a.doScroll();return document.id(a).inject(document.body).set("html","temp").dispose();}))?b():arguments.callee.delay(50);})(); }else{if(Browser.Engine.webkit&&Browser.Engine.version<525){(function(){(["loaded","complete"].contains(document.readyState))?b():arguments.callee.delay(50); })();}else{document.addEvent("DOMContentLoaded",b);}}})();var JSON=new Hash(this.JSON&&{stringify:JSON.stringify,parse:JSON.parse}).extend({$specialChars:{"\b":"\\b","\t":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"},$replaceChars:function(a){return JSON.$specialChars[a]||"\\u00"+Math.floor(a.charCodeAt()/16).toString(16)+(a.charCodeAt()%16).toString(16); },encode:function(b){switch($type(b)){case"string":return'"'+b.replace(/[\x00-\x1f\\"]/g,JSON.$replaceChars)+'"';case"array":return"["+String(b.map(JSON.encode).clean())+"]"; case"object":case"hash":var a=[];Hash.each(b,function(e,d){var c=JSON.encode(e);if(c){a.push(JSON.encode(d)+":"+c);}});return"{"+a+"}";case"number":case"boolean":return String(b); case false:return"null";}return null;},decode:function(string,secure){if($type(string)!="string"||!string.length){return null;}if(secure&&!(/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(string.replace(/\\./g,"@").replace(/"[^"\\\n\r]*"/g,""))){return null; }return eval("("+string+")");}});Native.implement([Hash,Array,String,Number],{toJSON:function(){return JSON.encode(this);}});var Cookie=new Class({Implements:Options,options:{path:false,domain:false,duration:false,secure:false,document:document},initialize:function(b,a){this.key=b; this.setOptions(a);},write:function(b){b=encodeURIComponent(b);if(this.options.domain){b+="; domain="+this.options.domain;}if(this.options.path){b+="; path="+this.options.path; }if(this.options.duration){var a=new Date();a.setTime(a.getTime()+this.options.duration*24*60*60*1000);b+="; expires="+a.toGMTString();}if(this.options.secure){b+="; secure"; }this.options.document.cookie=this.key+"="+b;return this;},read:function(){var a=this.options.document.cookie.match("(?:^|;)\\s*"+this.key.escapeRegExp()+"=([^;]*)"); return(a)?decodeURIComponent(a[1]):null;},dispose:function(){new Cookie(this.key,$merge(this.options,{duration:-1})).write("");return this;}});Cookie.write=function(b,c,a){return new Cookie(b,a).write(c); };Cookie.read=function(a){return new Cookie(a).read();};Cookie.dispose=function(b,a){return new Cookie(b,a).dispose();};var Swiff=new Class({Implements:[Options],options:{id:null,height:1,width:1,container:null,properties:{},params:{quality:"high",allowScriptAccess:"always",wMode:"transparent",swLiveConnect:true},callBacks:{},vars:{}},toElement:function(){return this.object; },initialize:function(l,m){this.instance="Swiff_"+$time();this.setOptions(m);m=this.options;var b=this.id=m.id||this.instance;var a=document.id(m.container); Swiff.CallBacks[this.instance]={};var e=m.params,g=m.vars,f=m.callBacks;var h=$extend({height:m.height,width:m.width},m.properties);var k=this;for(var d in f){Swiff.CallBacks[this.instance][d]=(function(n){return function(){return n.apply(k.object,arguments); };})(f[d]);g[d]="Swiff.CallBacks."+this.instance+"."+d;}e.flashVars=Hash.toQueryString(g);if(Browser.Engine.trident){h.classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"; e.movie=l;}else{h.type="application/x-shockwave-flash";h.data=l;}var j=''; }}j+="";this.object=((a)?a.empty():new Element("div")).set("html",j).firstChild;},replaces:function(a){a=document.id(a,true);a.parentNode.replaceChild(this.toElement(),a); return this;},inject:function(a){document.id(a,true).appendChild(this.toElement());return this;},remote:function(){return Swiff.remote.apply(Swiff,[this.toElement()].extend(arguments)); }});Swiff.CallBacks={};Swiff.remote=function(obj,fn){var rs=obj.CallFunction(''+__flash__argumentsToXML(arguments,2)+""); return eval(rs);};var Fx=new Class({Implements:[Chain,Events,Options],options:{fps:50,unit:false,duration:500,link:"ignore"},initialize:function(a){this.subject=this.subject||this; this.setOptions(a);this.options.duration=Fx.Durations[this.options.duration]||this.options.duration.toInt();var b=this.options.wait;if(b===false){this.options.link="cancel"; }},getTransition:function(){return function(a){return -(Math.cos(Math.PI*a)-1)/2;};},step:function(){var a=$time();if(a=(7-4*d)/11){e=c*c-Math.pow((11-6*d-11*f)/4,2); break;}}return e;},Elastic:function(b,a){return Math.pow(2,10*--b)*Math.cos(20*b*Math.PI*(a[0]||1)/3);}});["Quad","Cubic","Quart","Quint"].each(function(b,a){Fx.Transitions[b]=new Fx.Transition(function(c){return Math.pow(c,[a+2]); });});var Request=new Class({Implements:[Chain,Events,Options],options:{url:"",data:"",headers:{"X-Requested-With":"XMLHttpRequest",Accept:"text/javascript, text/html, application/xml, text/xml, */*"},async:true,format:false,method:"post",link:"ignore",isSuccess:null,emulation:true,urlEncoded:true,encoding:"utf-8",evalScripts:false,evalResponse:false,noCache:false},initialize:function(a){this.xhr=new Browser.Request(); this.setOptions(a);this.options.isSuccess=this.options.isSuccess||this.isSuccess;this.headers=new Hash(this.options.headers);},onStateChange:function(){if(this.xhr.readyState!=4||!this.running){return; }this.running=false;this.status=0;$try(function(){this.status=this.xhr.status;}.bind(this));this.xhr.onreadystatechange=$empty;if(this.options.isSuccess.call(this,this.status)){this.response={text:this.xhr.responseText,xml:this.xhr.responseXML}; this.success(this.response.text,this.response.xml);}else{this.response={text:null,xml:null};this.failure();}},isSuccess:function(){return((this.status>=200)&&(this.status<300)); },processScripts:function(a){if(this.options.evalResponse||(/(ecma|java)script/).test(this.getHeader("Content-type"))){return $exec(a);}return a.stripScripts(this.options.evalScripts); },success:function(b,a){this.onSuccess(this.processScripts(b),a);},onSuccess:function(){this.fireEvent("complete",arguments).fireEvent("success",arguments).callChain(); },failure:function(){this.onFailure();},onFailure:function(){this.fireEvent("complete").fireEvent("failure",this.xhr);},setHeader:function(a,b){this.headers.set(a,b); return this;},getHeader:function(a){return $try(function(){return this.xhr.getResponseHeader(a);}.bind(this));},check:function(){if(!this.running){return true; }switch(this.options.link){case"cancel":this.cancel();return true;case"chain":this.chain(this.caller.bind(this,arguments));return false;}return false;},send:function(k){if(!this.check(k)){return this; }this.running=true;var i=$type(k);if(i=="string"||i=="element"){k={data:k};}var d=this.options;k=$extend({data:d.data,url:d.url,method:d.method},k);var g=k.data,b=String(k.url),a=k.method.toLowerCase(); switch($type(g)){case"element":g=document.id(g).toQueryString();break;case"object":case"hash":g=Hash.toQueryString(g);}if(this.options.format){var j="format="+this.options.format; g=(g)?j+"&"+g:j;}if(this.options.emulation&&!["get","post"].contains(a)){var h="_method="+a;g=(g)?h+"&"+g:h;a="post";}if(this.options.urlEncoded&&a=="post"){var c=(this.options.encoding)?"; charset="+this.options.encoding:""; this.headers.set("Content-type","application/x-www-form-urlencoded"+c);}if(this.options.noCache){var f="noCache="+new Date().getTime();g=(g)?f+"&"+g:f; }var e=b.lastIndexOf("/");if(e>-1&&(e=b.indexOf("#"))>-1){b=b.substr(0,e);}if(g&&a=="get"){b=b+(b.contains("?")?"&":"?")+g;g=null;}this.xhr.open(a.toUpperCase(),b,this.options.async); this.xhr.onreadystatechange=this.onStateChange.bind(this);this.headers.each(function(m,l){try{this.xhr.setRequestHeader(l,m);}catch(n){this.fireEvent("exception",[l,m]); }},this);this.fireEvent("request");this.xhr.send(g);if(!this.options.async){this.onStateChange();}return this;},cancel:function(){if(!this.running){return this; }this.running=false;this.xhr.abort();this.xhr.onreadystatechange=$empty;this.xhr=new Browser.Request();this.fireEvent("cancel");return this;}});(function(){var a={}; ["get","post","put","delete","GET","POST","PUT","DELETE"].each(function(b){a[b]=function(){var c=Array.link(arguments,{url:String.type,data:$defined}); return this.send($extend(c,{method:b}));};});Request.implement(a);})();Element.Properties.send={set:function(a){var b=this.retrieve("send");if(b){b.cancel(); }return this.eliminate("send").store("send:options",$extend({data:this,link:"cancel",method:this.get("method")||"post",url:this.get("action")},a));},get:function(a){if(a||!this.retrieve("send")){if(a||!this.retrieve("send:options")){this.set("send",a); }this.store("send",new Request(this.retrieve("send:options")));}return this.retrieve("send");}};Element.implement({send:function(a){var b=this.get("send"); b.send({data:this,url:a||b.options.url});return this;}});Request.HTML=new Class({Extends:Request,options:{update:false,append:false,evalScripts:true,filter:false},processHTML:function(c){var b=c.match(/]*>([\s\S]*?)<\/body>/i); c=(b)?b[1]:c;var a=new Element("div");return $try(function(){var d=""+c+"",g;if(Browser.Engine.trident){g=new ActiveXObject("Microsoft.XMLDOM"); g.async=false;g.loadXML(d);}else{g=new DOMParser().parseFromString(d,"text/xml");}d=g.getElementsByTagName("root")[0];if(!d){return null;}for(var f=0,e=d.childNodes.length; f. Copyright (c) 2006-2009 Aaron Newton , Valerio Proietti & the MooTools team , MIT Style License. MooTools.More={version:"1.2.4.4",build:"6f6057dc645fdb7547689183b2311063bd653ddf"};Class.refactor=function(b,a){$each(a,function(e,d){var c=b.prototype[d]; if(c&&(c=c._origin)&&typeof e=="function"){b.implement(d,function(){var f=this.previous;this.previous=c;var g=e.apply(this,arguments);this.previous=f;return g; });}else{b.implement(d,e);}});return b;};Class.Mutators.Binds=function(a){return a;};Class.Mutators.initialize=function(a){return function(){$splat(this.Binds).each(function(b){var c=this[b]; if(c){this[b]=c.bind(this);}},this);return a.apply(this,arguments);};};Class.Occlude=new Class({occlude:function(c,b){b=document.id(b||this.element);var a=b.retrieve(c||this.property); if(a&&!$defined(this.occluded)){return this.occluded=a;}this.occluded=false;b.store(c||this.property,this);return this.occluded;}});Element.implement({measure:function(e){var g=function(h){return !!(!h||h.offsetHeight||h.offsetWidth); };if(g(this)){return e.apply(this);}var d=this.getParent(),f=[],b=[];while(!g(d)&&d!=document.body){b.push(d.expose());d=d.getParent();}var c=this.expose(); var a=e.apply(this);c();b.each(function(h){h();});return a;},expose:function(){if(this.getStyle("display")!="none"){return $empty;}var a=this.style.cssText; this.setStyles({display:"block",position:"absolute",visibility:"hidden"});return function(){this.style.cssText=a;}.bind(this);},getDimensions:function(a){a=$merge({computeSize:false},a); var f={};var d=function(g,e){return(e.computeSize)?g.getComputedSize(e):g.getSize();};var b=this.getParent("body");if(b&&this.getStyle("display")=="none"){f=this.measure(function(){return d(this,a); });}else{if(b){try{f=d(this,a);}catch(c){}}else{f={x:0,y:0};}}return $chk(f.x)?$extend(f,{width:f.x,height:f.y}):$extend(f,{x:f.width,y:f.height});},getComputedSize:function(a){a=$merge({styles:["padding","border"],plains:{height:["top","bottom"],width:["left","right"]},mode:"both"},a); var c={width:0,height:0};switch(a.mode){case"vertical":delete c.width;delete a.plains.width;break;case"horizontal":delete c.height;delete a.plains.height; break;}var b=[];$each(a.plains,function(g,f){g.each(function(h){a.styles.each(function(i){b.push((i=="border")?i+"-"+h+"-width":i+"-"+h);});});});var e={}; b.each(function(f){e[f]=this.getComputedStyle(f);},this);var d=[];$each(a.plains,function(g,f){var h=f.capitalize();c["total"+h]=c["computed"+h]=0;g.each(function(i){c["computed"+i.capitalize()]=0; b.each(function(k,j){if(k.test(i)){e[k]=e[k].toInt()||0;c["total"+h]=c["total"+h]+e[k];c["computed"+i.capitalize()]=c["computed"+i.capitalize()]+e[k];}if(k.test(i)&&f!=k&&(k.test("border")||k.test("padding"))&&!d.contains(k)){d.push(k); c["computed"+h]=c["computed"+h]-e[k];}});});});["Width","Height"].each(function(g){var f=g.toLowerCase();if(!$chk(c[f])){return;}c[f]=c[f]+this["offset"+g]+c["computed"+g]; c["total"+g]=c[f]+c["total"+g];delete c["computed"+g];},this);return $extend(e,c);}});(function(){var a=Element.prototype.position;Element.implement({position:function(g){if(g&&($defined(g.x)||$defined(g.y))){return a?a.apply(this,arguments):this; }$each(g||{},function(u,t){if(!$defined(u)){delete g[t];}});g=$merge({relativeTo:document.body,position:{x:"center",y:"center"},edge:false,offset:{x:0,y:0},returnPos:false,relFixedPosition:false,ignoreMargins:false,ignoreScroll:false,allowNegative:false},g); var r={x:0,y:0},e=false;var c=this.measure(function(){return document.id(this.getOffsetParent());});if(c&&c!=this.getDocument().body){r=c.measure(function(){return this.getPosition(); });e=c!=document.id(g.relativeTo);g.offset.x=g.offset.x-r.x;g.offset.y=g.offset.y-r.y;}var s=function(t){if($type(t)!="string"){return t;}t=t.toLowerCase(); var u={};if(t.test("left")){u.x="left";}else{if(t.test("right")){u.x="right";}else{u.x="center";}}if(t.test("upper")||t.test("top")){u.y="top";}else{if(t.test("bottom")){u.y="bottom"; }else{u.y="center";}}return u;};g.edge=s(g.edge);g.position=s(g.position);if(!g.edge){if(g.position.x=="center"&&g.position.y=="center"){g.edge={x:"center",y:"center"}; }else{g.edge={x:"left",y:"top"};}}this.setStyle("position","absolute");var f=document.id(g.relativeTo)||document.body,d=f==document.body?window.getScroll():f.getPosition(),l=d.y,h=d.x; var n=this.getDimensions({computeSize:true,styles:["padding","border","margin"]});var j={},o=g.offset.y,q=g.offset.x,k=window.getSize();switch(g.position.x){case"left":j.x=h+q; break;case"right":j.x=h+q+f.offsetWidth;break;default:j.x=h+((f==document.body?k.x:f.offsetWidth)/2)+q;break;}switch(g.position.y){case"top":j.y=l+o;break; case"bottom":j.y=l+o+f.offsetHeight;break;default:j.y=l+((f==document.body?k.y:f.offsetHeight)/2)+o;break;}if(g.edge){var b={};switch(g.edge.x){case"left":b.x=0; break;case"right":b.x=-n.x-n.computedRight-n.computedLeft;break;default:b.x=-(n.totalWidth/2);break;}switch(g.edge.y){case"top":b.y=0;break;case"bottom":b.y=-n.y-n.computedTop-n.computedBottom; break;default:b.y=-(n.totalHeight/2);break;}j.x+=b.x;j.y+=b.y;}j={left:((j.x>=0||e||g.allowNegative)?j.x:0).toInt(),top:((j.y>=0||e||g.allowNegative)?j.y:0).toInt()}; var i={left:"x",top:"y"};["minimum","maximum"].each(function(t){["left","top"].each(function(u){var v=g[t]?g[t][i[u]]:null;if(v!=null&&j[u]this.options.snap){this.cancel();this.document.addEvents({mousemove:this.bound.drag,mouseup:this.bound.stop});this.fireEvent("start",[this.element,a]).fireEvent("snap",this.element); }},drag:function(a){if(this.options.preventDefault){a.preventDefault();}this.mouse.now=a.page;for(var b in this.options.modifiers){if(!this.options.modifiers[b]){continue; }this.value.now[b]=this.mouse.now[b]-this.mouse.pos[b];if(this.options.invert){this.value.now[b]*=-1;}if(this.options.limit&&this.limit[b]){if($chk(this.limit[b][1])&&(this.value.now[b]>this.limit[b][1])){this.value.now[b]=this.limit[b][1]; }else{if($chk(this.limit[b][0])&&(this.value.now[b]0)^(a0)^(a>this.max))){a=this.max;}this.step=Math.round(a);this.checkStep();this.fireEvent("tick",this.toPosition(this.step));this.end();return this; },clickedElement:function(c){if(this.isDragging||c.target==this.knob){return;}var b=this.range<0?-1:1;var a=c.page[this.axis]-this.element.getPosition()[this.axis]-this.half; a=a.limit(-this.options.offset,this.full-this.options.offset);this.step=Math.round(this.min+b*this.toStep(a));this.checkStep();this.fireEvent("tick",a); this.end();},scrolledElement:function(a){var b=(this.options.mode=="horizontal")?(a.wheel<0):(a.wheel>0);this.set(b?this.step-this.stepSize:this.step+this.stepSize); a.stop();},draggedKnob:function(){var b=this.range<0?-1:1;var a=this.drag.value.now[this.axis];a=a.limit(-this.options.offset,this.full-this.options.offset); this.step=Math.round(this.min+b*this.toStep(a));this.checkStep();},checkStep:function(){if(this.previousChange!=this.step){this.previousChange=this.step; this.fireEvent("change",this.step);}},end:function(){if(this.previousEnd!==this.step){this.previousEnd=this.step;this.fireEvent("complete",this.step+""); }},toStep:function(a){var b=(a+this.options.offset)*this.stepSize/this.full*this.steps;return this.options.steps?Math.round(b-=b%this.stepSize):b;},toPosition:function(a){return(this.full*Math.abs(this.min-a))/(this.steps*this.stepSize)-this.options.offset; }});var Asset={javascript:function(f,d){d=$extend({onload:$empty,document:document,check:$lambda(true)},d);if(d.onLoad){d.onload=d.onLoad;}var b=new Element("script",{src:f,type:"text/javascript"}); var e=d.onload.bind(b),a=d.check,g=d.document;delete d.onload;delete d.check;delete d.document;b.addEvents({load:e,readystatechange:function(){if(["loaded","complete"].contains(this.readyState)){e(); }}}).set(d);if(Browser.Engine.webkit419){var c=(function(){if(!$try(a)){return;}$clear(c);e();}).periodical(50);}return b.inject(g.head);},css:function(b,a){return new Element("link",$merge({rel:"stylesheet",media:"screen",type:"text/css",href:b},a)).inject(document.head); },image:function(c,b){b=$merge({onload:$empty,onabort:$empty,onerror:$empty},b);var d=new Image();var a=document.id(d)||new Element("img");["load","abort","error"].each(function(e){var g="on"+e; var f=e.capitalize();if(b["on"+f]){b[g]=b["on"+f];}var h=b[g];delete b[g];d[g]=function(){if(!d){return;}if(!a.parentNode){a.width=d.width;a.height=d.height; }d=d.onload=d.onabort=d.onerror=null;h.delay(1,a,a);a.fireEvent(e,a,1);};});d.src=a.src=c;if(d&&d.complete){d.onload.delay(1);}return a.set(b);},images:function(d,c){c=$merge({onComplete:$empty,onProgress:$empty,onError:$empty,properties:{}},c); d=$splat(d);var a=[];var b=0;return new Elements(d.map(function(e){return Asset.image(e,$extend(c.properties,{onload:function(){c.onProgress.call(this,b,d.indexOf(e)); b++;if(b==d.length){c.onComplete();}},onerror:function(){c.onError.call(this,b,d.indexOf(e));b++;if(b==d.length){c.onComplete();}}}));}));}};var IframeShim=new Class({Implements:[Options,Events,Class.Occlude],options:{className:"iframeShim",src:'javascript:false;document.write("");',display:false,zIndex:null,margin:0,offset:{x:0,y:0},browsers:(Browser.Engine.trident4||(Browser.Engine.gecko&&!Browser.Engine.gecko19&&Browser.Platform.mac))},property:"IframeShim",initialize:function(b,a){this.element=document.id(b); if(this.occlude()){return this.occluded;}this.setOptions(a);this.makeShim();return this;},makeShim:function(){if(this.options.browsers){var c=this.element.getStyle("zIndex").toInt(); if(!c){c=1;var b=this.element.getStyle("position");if(b=="static"||!b){this.element.setStyle("position","relative");}this.element.setStyle("zIndex",c); }c=($chk(this.options.zIndex)&&c>this.options.zIndex)?this.options.zIndex:c-1;if(c<0){c=1;}this.shim=new Element("iframe",{src:this.options.src,scrolling:"no",frameborder:0,styles:{zIndex:c,position:"absolute",border:"none",filter:"progid:DXImageTransform.Microsoft.Alpha(style=0,opacity=0)"},"class":this.options.className}).store("IframeShim",this); var a=(function(){this.shim.inject(this.element,"after");this[this.options.display?"show":"hide"]();this.fireEvent("inject");}).bind(this);if(!IframeShim.ready){window.addEvent("load",a); }else{a();}}else{this.position=this.hide=this.show=this.dispose=$lambda(this);}},position:function(){if(!IframeShim.ready||!this.shim){return this;}var a=this.element.measure(function(){return this.getSize(); });if(this.options.margin!=undefined){a.x=a.x-(this.options.margin*2);a.y=a.y-(this.options.margin*2);this.options.offset.x+=this.options.margin;this.options.offset.y+=this.options.margin; }this.shim.set({width:a.x,height:a.y}).position({relativeTo:this.element,offset:this.options.offset});return this;},hide:function(){if(this.shim){this.shim.setStyle("display","none"); }return this;},show:function(){if(this.shim){this.shim.setStyle("display","block");}return this.position();},dispose:function(){if(this.shim){this.shim.dispose(); }return this;},destroy:function(){if(this.shim){this.shim.destroy();}return this;}});window.addEvent("load",function(){IframeShim.ready=true;});var Mask=new Class({Implements:[Options,Events],Binds:["position"],options:{style:{},"class":"mask",maskMargins:false,useIframeShim:true,iframeShimOptions:{}},initialize:function(b,a){this.target=document.id(b)||document.id(document.body); this.target.store("Mask",this);this.setOptions(a);this.render();this.inject();},render:function(){this.element=new Element("div",{"class":this.options["class"],id:this.options.id||"mask-"+$time(),styles:$merge(this.options.style,{display:"none"}),events:{click:function(){this.fireEvent("click"); if(this.options.hideOnClick){this.hide();}}.bind(this)}});this.hidden=true;},toElement:function(){return this.element;},inject:function(b,a){a=a||this.options.inject?this.options.inject.where:""||this.target==document.body?"inside":"after"; b=b||this.options.inject?this.options.inject.target:""||this.target;this.element.inject(b,a);if(this.options.useIframeShim){this.shim=new IframeShim(this.element,this.options.iframeShimOptions); this.addEvents({show:this.shim.show.bind(this.shim),hide:this.shim.hide.bind(this.shim),destroy:this.shim.destroy.bind(this.shim)});}},position:function(){this.resize(this.options.width,this.options.height); this.element.position({relativeTo:this.target,position:"topLeft",ignoreMargins:!this.options.maskMargins,ignoreScroll:this.target==document.body});return this; },resize:function(a,e){var b={styles:["padding","border"]};if(this.options.maskMargins){b.styles.push("margin");}var d=this.target.getComputedSize(b);if(this.target==document.body){var c=window.getSize(); if(d.totalHeightàÔµ|·±ÎÜnÒl~Œ«Àpå'®u#P€+È%Æç4¶ ¹N¯AöŠÍ"šÓÐÖƤêƒ[*¡5*ZÓ8 ªNÙ’ÖÎè´ª9ÔiE;A§‘v6’Nmí}À1—zæéžA  0XKRÊôvv'K°õájê]¾^¤¡§ø2ÚcùÁ8õʨ·ô{еö:;‰+íƒ:Möb[Ú¿ê´ñ5£«]J§­/jUíÒ:‡*ôÿÅ> øçï pŽKC'ßBIEND®B`‚pfstools-2.2.0/src/hdrhtml/hdrhtml_hdrlabs_templ/hdrhtml_assets/information.png0000664000701400070140000000240614105165614026735 0ustar rkm38rkm38‰PNG  IHDR‰ îiCCPICC Profilex…TÏkAþ6n©Ð"Zk²x"IY«hEÔ6ýbk Û¶Ed3IÖn6ëî&µ¥ˆäâÑ*ÞEí¡ÿ€zðd/J…ZE(Þ«(b¡-ñÍnL¶¥êÀÎ~óÞ7ï}ovß rÒ4õ€ä ÇR¢il|BjüˆŽ¢ A4%UÛìN$Aƒsù{çØz[VÃ{ûw²w­šÒ¶š„ý@àGšÙ*°ïq Yˆ<ß¡)ÇtßãØòì9NyxÁµ+=ÄY"|@5-ÎM¸SÍ%Ó@ƒH8”õqR>œ×‹”×infÆÈ½O¦»Ìî«b¡œNö½ô~N³Þ>Â! ­?F¸žõŒÕ?âaá¤æÄ†=5ôø`·©ø5Â_M'¢TqÙ. ñ˜®ýVòJ‚p8Êda€sZHO×Lnøº‡}&ׯâwVQáygÞÔÝïEÚ¯0  š HPEa˜°P@†<14²r?#«“{2u$j»tbD±A{6Ü=·Q¤Ý<þ("q”Cµ’üAþ*¯ÉOåyùË\°ØV÷”­›šºòà;Å噹×ÓÈãsM^|•Ôv“WG–¬yz¼šì?ìW—1æ‚5Äs°ûñ-_•Ì—)ŒÅãUóêK„uZ17ߟl;=â.Ï.µÖs­‰‹7V›—gýjHû“æUùO^õñügÍÄcâ)1&vŠç!‰—Å.ñ’ØK« â`mÇ•†)Òm‘ú$Õ``š¼õ/]?[x½F õQ”ÌÒT‰÷Â*d4¹oúÛÇüä÷ŠçŸ(/làÈ™ºmSqï¡e¥ns®¿Ñ}ð¶nk£~8üX<«­R5Ÿ ¼v‡zè)˜Ó––Í9R‡,Ÿ“ºéÊbRÌPÛCRR×%×eK³™UbévØ™Ón¡9B÷ħJe“ú¯ñ°ý°Rùù¬RÙ~NÖ—úoÀ¼ýEÀx‹‰ÓIDAT8”¿+…QǽºÊ€¥2ˆÑl0`“ÁÀ"›ÝH•Ád²H)ð×rI ˆH!¹>ßÛ9õz<~<õ¹çœçÇ÷}Þ÷žs’¢_,ŸÏ·n„êvÇxž$I6¬ÿ7 Ô ëƒWHÛ‹ èûS¤˜ƒð,‹SÈô åÖ¸Âê`~²Uz`%̤’TS÷Mç|*ÉN?pŒÅ"æÃ&a>Æ #Án°ßÊÔä÷qtA?˜ j»£X†Å¦I°Ë#‹°o6ÖÒÈñÓúç<»Ã9 ú¾z°¾ßx&¶bÚl‚ÚB»_.YNA T°÷Þáùx&& VAÆÉØÃ7p­843´kî˜4ª<¡˜;ÀDÝMÐYìJ¾ú˜àêðÞ ÄNaI1º+gÐéPgÒ¸W07àÙ Ý݆@+c<{‰ø¤‘“à‚µËrÒò†àåkÒV¡ ‡"»±u¤ÊB\[fFA{Òšj{ bñ‡=zkøJƒ Îð\€g_^(²—Ã#•³0 ÛžJðù—Cª“íë{zÊQ®}ÅWÂ}$êÕ+¦Ï®æòýxÁ&i!;§P[¥bÚBl¥›ןáæwà0÷IEND®B`‚pfstools-2.2.0/src/hdrhtml/hdrhtml_hdrlabs_templ/hdrhtml_assets/mouse2touch.js0000664000701400070140000000200714105165614026512 0ustar rkm38rkm38/* --- description: A MooTools plugin that automatically map mouse events to touch events license: MIT-style authors: - Chi Wai Lau (http://tabqwert.com) - Scott Kyle (http://appden.com) requires: - core/1.2.4: '*' provides: [Mouse2Touch] ... */ if((navigator.userAgent.match(/iPhone/i)) || (navigator.userAgent.match(/iPod/i)) || (navigator.userAgent.match(/iPad/i))) { (function() { try { document.createEvent("TouchEvent"); } catch(e) { return; } ['touchstart', 'touchmove', 'touchend'].each(function(type){ Element.NativeEvents[type] = 2; }); var mapping = { 'mousedown': 'touchstart', 'mousemove': 'touchmove', 'mouseup': 'touchend' }; var condition = function(event) { var touch = event.event.changedTouches[0]; event.page = { x: touch.pageX, y: touch.pageY }; return true; }; for (var e in mapping) { Element.Events[e] = { base: mapping[e], condition: condition }; } })(); } pfstools-2.2.0/src/hdrhtml/hdrhtml_hdrlabs_templ/hdrhtml_image_templ_slider-above.html0000664000701400070140000000175214105165614030210 0ustar rkm38rkm38
 
 
 
pfstools-2.2.0/src/hdrhtml/hdrhtml_hdrlabs_templ/hdrhtml_image_templ.html0000664000701400070140000000173614105165614025556 0ustar rkm38rkm38
 
 
 
pfstools-2.2.0/src/hdrhtml/hdrhtml_hdrlabs_templ/hdrhtml_page_templ_short.html0000664000701400070140000000251414105165614026622 0ustar rkm38rkm38 @title@ @image_htmlcode@ pfstools-2.2.0/src/hdrhtml/hdrhtml.cpp0000664000701400070140000006317514105165614016474 0ustar rkm38rkm38/** * @brief Create a web page with an HDR viewer * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2009 Rafal Mantiuk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Rafal Mantiuk, * * $Id: hdrhtml.cpp,v 1.8 2014/06/16 21:50:08 rafm Exp $ */ #include "hdrhtml.h" #include #include #include #include #include #include #include #include // This is to get rid of warnings due to double defines from Magick++ #undef PACKAGE #undef PACKAGE_BUGREPORT #undef PACKAGE_NAME #undef PACKAGE_STRING #undef PACKAGE_TARNAME #undef PACKAGE_VERSION #undef VERSION #include using namespace std; // ================================================ // Parameters controllig the web page // ================================================ const int f_step_res = 3; // How many steps per f-stop (do not change) const int pix_per_fstop = 25; // Distance in pixels between f-stops shown on the histogram const char *hdrhtml_version = "1.0"; // Version of the HDRHTML code // ================================================ // Histogram // ================================================ template class Histogram { public: T *x; // Bin centers size_t *n; // No of items in a bin size_t bins; Histogram() : x( NULL ), n( NULL ) { } ~Histogram() { free(); } void free() { delete []x; delete []n; bins = 0; } void compute( const T *data, size_t d_size, int bins = 30, T min_val = 1, T max_val = -1, bool reject_outofrange = true ) { assert( bins > 0 ); free(); this->bins = bins; if( min_val > max_val ) // missing min/max info { min_val = numeric_limits::max(); max_val = numeric_limits::min(); for( int k=0; k < d_size; k++ ) { if( data[k] > max_val ) max_val = data[k]; if( data[k] < min_val ) min_val = data[k]; } } x = new T[bins]; n = new size_t[bins]; T delta = (max_val-min_val) / (float)bins; // width of a single bin // T *e = new T[bins+1]; // bin edges // for( int k=0; k <= bins; k++ ) { // e[k] = min_val + (float)k * delta; // } for( int k=0; k < bins; k++ ) { x[k] = min_val + (float)k * delta + delta/2; n[k] = 0; } if( reject_outofrange ) { for( int k=0; k < d_size; k++ ) { int ind = floor( (data[k]-min_val) / (max_val-min_val) * (float)bins ); if( ind < 0 ) continue; if( ind >= bins ) continue; n[ind]++; } } else { for( int k=0; k < d_size; k++ ) { int ind = floor( (data[k]-min_val) / (max_val-min_val) * (float)bins ); if( ind < 0 ) { n[0]++; continue; } if( ind >= bins ) { n[bins-1]++; continue; } n[ind]++; } } } }; // ================================================ // Lookup table // ================================================ /** * Lookup table on a uniform array & interpolation * * x_i must be at least two elements * y_i must be initialized after creating an object */ class UniformArrayLUT { const float *x_i; size_t lut_size; float delta; bool own_y_i; public: float *y_i; UniformArrayLUT( size_t lut_size, const float *x_i, float *y_i = NULL ) : x_i( x_i ), lut_size( lut_size ), delta( x_i[1]-x_i[0] ) { if( y_i == NULL ) { this->y_i = new float[lut_size]; own_y_i = true; } else { this->y_i = y_i; own_y_i = false; } } UniformArrayLUT() : x_i( 0 ), y_i(0), lut_size( 0 ), delta( 0. ) {} UniformArrayLUT(const UniformArrayLUT& other) : x_i( other.x_i ), lut_size( other.lut_size ), delta( other.delta ) { this->y_i = new float[lut_size]; own_y_i = true; memcpy(this->y_i, other.y_i, lut_size * sizeof(float)); } UniformArrayLUT& operator = (const UniformArrayLUT& other) { this->lut_size = other.lut_size; this->delta = other.delta; this->x_i = other.x_i; this->y_i = new float[lut_size]; own_y_i = true; memcpy(this->y_i, other.y_i, lut_size * sizeof(float)); } ~UniformArrayLUT() { if( own_y_i ) delete []y_i; } float interp( float x ) { const float ind_f = (x - x_i[0])/delta; const size_t ind_low = (size_t)(ind_f); const size_t ind_hi = (size_t)ceil(ind_f); if( (ind_f < 0) ) // Out of range checks return y_i[0]; if( (ind_hi >= lut_size) ) return y_i[lut_size-1]; if( (ind_low == ind_hi) ) return y_i[ind_low]; // No interpolation necessary return y_i[ind_low] + (y_i[ind_hi]-y_i[ind_low])*(ind_f-(float)ind_low); // Interpolation } }; template inline T clamp( T x, T min, T max ) { if( x < min ) return min; if( x > max ) return max; return x; } /** * Lookup table on an arbitrary array * * x_i must be at least two elements * y_i must be initialized after creating an object */ template class ArrayLUT { const Tx *x_i; size_t lut_size; bool own_y_i; public: Ty *y_i; ArrayLUT( size_t lut_size, const Tx *x_i, Ty *y_i = NULL ) : x_i( x_i ), lut_size( lut_size ) { assert( lut_size > 0 ); if( y_i == NULL ) { this->y_i = new Ty[lut_size]; own_y_i = true; } else { this->y_i = y_i; own_y_i = false; } } ArrayLUT() : x_i( 0 ), y_i(0), lut_size( 0 ) {} ArrayLUT(const ArrayLUT& other) : x_i( other.x_i ), lut_size( other.lut_size ) { this->y_i = new Ty[lut_size]; own_y_i = true; memcpy(this->y_i, other.y_i, lut_size * sizeof(Ty)); } ArrayLUT& operator = (const ArrayLUT& other) { this->lut_size = other.lut_size; this->x_i = other.x_i; this->y_i = new Ty[lut_size]; own_y_i = true; memcpy(this->y_i, other.y_i, lut_size * sizeof(Ty)); } ~ArrayLUT() { if( own_y_i ) delete []y_i; } Ty interp( Tx x ) { if( (x <= x_i[0]) ) // Out of range checks return y_i[0]; if( (x >= x_i[lut_size-1]) ) return y_i[lut_size-1]; // binary search size_t l = 0, r = lut_size-1; while( true ) { size_t m = (l+r)/2; if( m == l ) break; if( x < x_i[m] ) r = m; else l = m; } float alpha = (x - x_i[l])/(x_i[r]-x_i[l]); return y_i[l] + (Ty)(alpha * (y_i[r]-y_i[l])); } }; // ================================================ // Percentiles // ================================================ /** * Compute prctiles using image cummulative histogram, which is less * accurate method but much faster than sorting. */ template class Percentiles { Histogram hist; const size_t bin_n; size_t d_size; public: /** * @param data - table with samples * @param d_size - number of samples */ Percentiles( const T *data, size_t d_size ) : bin_n( 1000 ), d_size( d_size ) // Accuracy 0.1 prctile { hist.compute( data, d_size, bin_n, 1, -1, false ); // Compute cummulative histogram for( int k = 1; k < bin_n; k++ ) hist.n[k] += hist.n[k-1]; // cerr << "d_size: " << d_size << " hist.n: " << hist.n[bin_n-1] << "\n"; assert( hist.n[bin_n-1] == d_size ); } T prctile( double p ) { ArrayLUT lut( hist.bins, hist.n, hist.x ); return lut.interp( (size_t)(p*(double)d_size/100.) ); } }; // ================================================ // Text template file utils // ================================================ typedef void (*replace_callback)( ostream &out, void *user_data, const char *parameter ); class ReplacePattern { public: const char* pattern; std::string replace_with; replace_callback callback; void *user_data; ReplacePattern( const char* pattern, std::string replace_with ) : pattern( pattern ), replace_with( replace_with ), callback( NULL ) { } ReplacePattern( const char* pattern, float replace_with_num ) : pattern( pattern ), callback( NULL ) { std::ostringstream num_str; num_str << replace_with_num; replace_with = num_str.str(); } ReplacePattern( const char* pattern, int replace_with_num ) : pattern( pattern ), callback( NULL ) { std::ostringstream num_str; num_str << replace_with_num; replace_with = num_str.str(); } ReplacePattern( const char* pattern, replace_callback callback, void *user_data = NULL ) : pattern( pattern ), callback( callback ), user_data( user_data ) { } ReplacePattern() : pattern( NULL ), callback( NULL ) { } virtual void write_replacement( ostream &out, const char *parameter = NULL ) { if( callback != NULL ) callback( out, user_data, parameter ); else out << replace_with; } }; void create_from_template( std::ostream &outfs, const char *template_file_name, ReplacePattern *pattern_list ) { std::ifstream infs( template_file_name ); if( !infs.good() ) { std::ostringstream error_message; error_message << "Cannot open '" << template_file_name << "' for reading"; throw pfs::Exception( error_message.str().c_str() ); } const int MAX_LINE_LENGTH = 2048; // int lines = 0; while( true ) { char line[MAX_LINE_LENGTH]; infs.getline( line, MAX_LINE_LENGTH ); if( !infs.good() ) break; std::string line_str( line ); int pos = 0; while( true ) { int find_pos = line_str.find_first_of( '@', pos ); if( find_pos == std::string::npos ) { outfs << line_str.substr( pos, std::string::npos ); break; } bool replaced = false; int end_marker = line_str.find_first_of( "@[", find_pos+1 ); if( end_marker != std::string::npos ) { for( int k = 0; pattern_list[k].pattern != NULL; k++ ) { if( line_str.compare( find_pos+1, end_marker-find_pos-1, pattern_list[k].pattern ) == 0 ) { outfs << line_str.substr( pos, find_pos-pos ); std::string parameter; if( line_str[end_marker] == '[' ) { int param_endmarker = line_str.find_first_of( ']', end_marker+1 ); if( param_endmarker == std::string::npos ) throw pfs::Exception( "Non-closed bracker in the replacement keyword" ); parameter = line_str.substr( end_marker+1, param_endmarker-end_marker-1 ); end_marker = param_endmarker+1; } pattern_list[k].write_replacement( outfs, parameter.empty() ? NULL : parameter.c_str() ); pos = end_marker + 1; replaced = true; break; } } } if( !replaced ) { outfs << line_str.substr( pos, find_pos-pos+1 ); pos = find_pos+1; } } outfs << "\n"; } } void create_from_template( const char *output_file_name, const char *template_file_name, ReplacePattern *pattern_list ) { std::ofstream outfs( output_file_name ); if( !outfs.good() ) { std::ostringstream error_message; error_message << "Cannot open '" << output_file_name << "' for writing"; throw pfs::Exception( error_message.str().c_str() ); } create_from_template( outfs, template_file_name, pattern_list ); } // ================================================ // Read and parse CVS files // ================================================ class CSVTable { public: float **data; int columns, rows; CSVTable() : data( NULL ) { } ~CSVTable() { free(); } void free() { if( data == NULL ) return; for( int k = 0; k < columns; k++ ) delete [] data[k]; delete []data; data = NULL; } void read( const char *file_name, int columns ) { free(); this->columns = columns; std::ifstream ifs( file_name ); if( !ifs.is_open() ) { std::string full_message( "Cannot open file: " ); full_message += file_name; throw pfs::Exception( full_message.c_str() ); } std::list value_list; const int MAX_LINE_LENGTH = 1024; int lines = 0; while( 1 ) { char line[MAX_LINE_LENGTH]; ifs.getline( line, MAX_LINE_LENGTH ); if( !ifs.good() ) break; std::string line_str( line ); int pos = 0; for( int k=0; k < columns; k++ ) { // Skip white spaces while( line_str[pos] == ' ' || line_str[pos] == '\t' ) pos++; int new_pos = line_str.find_first_of( ',', pos ); size_t len; if( new_pos == std::string::npos ) { if( k != columns-1 ) { std::string full_message( "Missing column data in the file: " ); full_message += file_name; throw pfs::Exception( full_message.c_str() ); } len = std::string::npos; } else len = new_pos-pos; float value; if( len == 0 ) { value = numeric_limits::quiet_NaN(); } else { std::string token = line_str.substr( pos, len ); const char *str_beg = token.c_str(); char *str_end; // cerr << "token: " << str_beg << "\n"; value = strtof( str_beg, &str_end ); if( str_beg == str_end ) { std::ostringstream error_message; error_message << "Error parsing line " << lines+1 << " of " << file_name << "\n"; throw pfs::Exception( error_message.str().c_str() ); } } value_list.push_back( value ); pos = new_pos+1; } lines++; } float **table = new float*[columns]; for( int c=0; c < columns; c++ ) table[c] = new float[lines]; for( int l=0; l < lines; l++ ) for( int c=0; c < columns; c++ ) { table[c][l] = value_list.front(); value_list.pop_front(); } data = table; this->rows = lines; } }; // ================================================ // HDR HTML code // ================================================ void HDRHTMLSet::add_image( int width, int height, float *R, float *G, float *B, float *Y, const char *base_name, int quality ) { const int pixels = width*height; const int basis_no = quality; // Load LUT for the basis tone-curves std::ostringstream lut_filename; lut_filename << PKG_DATADIR "/hdrhtml_t_b" << basis_no+1 << ".csv"; CSVTable basis_table; basis_table.read( lut_filename.str().c_str(), basis_no+1 ); // Transform the first row (luminance factors) to the log domain for( int k = 0; k < basis_table.rows; k++ ) basis_table.data[0][k] = log2f( basis_table.data[0][k] ); // Fix zero and negative values in the image, convert to log2 space, find min and max values float img_min = numeric_limits::max(); float img_max = numeric_limits::min(); { float *arrays[] = { R, G, B, Y }; int k; for( k = 0; k < 4; k++ ) { float *x = arrays[k]; float min_val = numeric_limits::max(), max_val = numeric_limits::min(); for( int i=0; i < pixels; i++ ) { if( x[i] < min_val && x[i] > 0) min_val = x[i]; if( x[i] > max_val ) max_val = x[i]; } img_max = std::max( img_max, log2f(max_val) ); img_min = std::min( img_min, log2f(min_val) ); for( int i=0; i < pixels; i++ ) { if( x[i] < min_val ) x[i] = log2f(min_val); else x[i] = log2f(x[i]); } } } Percentiles prc( Y, pixels ); img_min = prc.prctile( 0.1 ); img_max = prc.prctile( 99.9 ); img_min -= 4; // give extra room for brightenning // how many 8-fstop segments we need to cover the DR int f8_stops = ceil((img_max-img_min)/8); // start with this f-stop float l_start = img_min + (img_max-img_min-f8_stops*8)/2; float l_med = prc.prctile( 50 ); float best_exp = round(l_med-l_start-4); // pix_per_fstop = 25; // % generate image histogram const int hist_height = 36; int hist_width = width; // float hist_img_sz = [36 size(img,2)]; float hist_fstops = hist_width / pix_per_fstop; float hist_start = (img_max-img_min-hist_fstops)/2; { Histogram hist; hist.compute( Y, pixels, hist_width, img_min+hist_start, img_min+hist_start+hist_fstops ); unsigned short *hist_buffer = new unsigned short[hist_width*hist_height*3]; float hist_n_max = -1; for( int k = 0; k < hist_width; k++ ) hist_n_max = std::max( hist_n_max, (float)hist.n[k] ); for( int k = 0; k < hist_width; k++ ) { float top = hist_height - round((float)hist.n[k]/hist_n_max * hist_height); for( int r = 0; r < hist_height; r++ ) { hist_buffer[(r*hist_width+k)*3+0] = 0; hist_buffer[(r*hist_width+k)*3+1] = (r>=top ? (1<<16) -1 : 0); hist_buffer[(r*hist_width+k)*3+2] = 0; } } // tick_fstops = (floor(hist_l(end))-ceil(hist_l(1))); // ticks = round((ceil(hist_l(1))-hist_l(1))*pix_per_fstop) + (1:tick_fstops)*pix_per_fstop; // hist_img(1:5,ticks,1:2) = 0.5; // hist_img(end-4:end,ticks,1:2) = 0.5; // plot_name = sprintf( '%s_hist.png', base_name ); // imwrite( hist_img, plot_name ); Magick::Image hist_image( hist_width, hist_height, "RGB", Magick::ShortPixel, hist_buffer ); std::ostringstream img_filename; if( image_dir != NULL ) img_filename << image_dir << "/"; img_filename << base_name << "_hist.png"; std::cerr << "Writing: " << img_filename.str() << "\n"; hist_image.write( img_filename.str().c_str() ); delete []hist_buffer; } // generate basis images unsigned short *imgBuffer = new unsigned short[pixels*3]; for( int k=1; k <= f8_stops+1; k++ ) { float max_value = (float)numeric_limits::max(); //(1<<16) -1; float exp_multip = log2f(1/powf( 2, l_start + k*8 )); int max_basis = basis_no; if( k == f8_stops+1 ) // Do only one shared basis for the last 8-fstop segment max_basis = 1; for( int b=0; b < max_basis; b++ ) { UniformArrayLUT basis_lut( basis_table.rows, basis_table.data[0], basis_table.data[b+1] ); int i = 0; for( int pix = 0; pix < pixels; pix++ ) { float rgb[3]; rgb[0] = R[pix]; rgb[1] = G[pix]; rgb[2] = B[pix]; for( int c=0; c < 3; c++ ) { float exposure_comp_v = rgb[c] + exp_multip; float v = (basis_lut.interp(exposure_comp_v)*max_value); imgBuffer[i++] = (unsigned short)(basis_lut.interp(exposure_comp_v)*max_value); } } Magick::Image imImage( width, height, "RGB", Magick::ShortPixel, imgBuffer ); std::ostringstream img_filename; if( image_dir != NULL ) img_filename << image_dir << "/"; img_filename << base_name << '_' << k-1 << '_' << b+1 << ".jpg"; std::cerr << "Writing: " << img_filename.str() << "\n"; imImage.write( img_filename.str().c_str() ); } } delete [] imgBuffer; HDRHTMLImage new_image( base_name, width, height ); new_image.hist_width = hist_width; new_image.f8_stops = f8_stops; new_image.f_step_res = f_step_res; new_image.basis = basis_no; new_image.shared_basis = 1; new_image.pix_per_fstop = pix_per_fstop; new_image.hist_start = hist_start; new_image.hist_width = hist_width; new_image.best_exp = best_exp; image_list.push_back( new_image ); } void print_image_objects( ostream &out, void *user_data, const char *parameter ); void print_cf_table( ostream &out, void *user_data, const char *parameter ); void print_image_htmlcode( ostream &out, void *user_data, const char *parameter ); void HDRHTMLSet::generate_webpage( const char *page_template, const char *image_template, const char *object_output, const char *html_output) { if( image_list.empty() ) return; std::ostringstream out_file_name; if( page_name == NULL ) out_file_name << image_list.front().base_name << ".html"; else out_file_name << page_name; // Load the table of the opacity coeffcients std::ostringstream lut_filename; lut_filename << PKG_DATADIR "/hdrhtml_c_b" << image_list.front().basis+1 << ".csv"; CSVTable coeff_table; coeff_table.read( lut_filename.str().c_str(), image_list.front().basis+1 ); ReplacePattern replace_list[] = { ReplacePattern( "cf_array_def", print_cf_table, &coeff_table ), ReplacePattern( "hdr_img_def", print_image_objects, this ), ReplacePattern( "image_htmlcode", print_image_htmlcode, this ), ReplacePattern( "title", page_name == NULL ? "HDRHTML viewer" : page_name ), ReplacePattern( "version", hdrhtml_version ), ReplacePattern() }; this->image_template = image_template; create_from_template( out_file_name.str().c_str(), page_template, replace_list ); if( object_output != NULL ) { std::ofstream oofs( object_output ); if( !oofs.good() ) { std::ostringstream error_message; error_message << "Cannot open '" << object_output << "' for writing"; throw pfs::Exception( error_message.str().c_str() ); } print_image_objects( oofs, this, NULL ); } if( html_output != NULL ) { std::ofstream hofs( html_output ); if( !hofs.good() ) { std::ostringstream error_message; error_message << "Cannot open '" << html_output << "' for writing"; throw pfs::Exception( error_message.str().c_str() ); } print_image_htmlcode( hofs, this, NULL ); } } void print_image_objects( ostream &out, void *user_data, const char *parameter ) { HDRHTMLSet *hdrhtml_set = (HDRHTMLSet*)user_data; list::iterator it; for( it = hdrhtml_set->image_list.begin(); it != hdrhtml_set->image_list.end(); it++ ) { std::string obj_name( "hdr_" ); obj_name.append( it->base_name ); out << obj_name << " = new Object();\n"; out << obj_name << ".width = " << it->width << ";\n"; out << obj_name << ".height = " << it->height << ";\n"; out << obj_name << ".f8_stops = " << it->f8_stops << ";\n"; out << obj_name << ".f_step_res = " << it->f_step_res << ";\n"; out << obj_name << ".base_name = \"" << it->base_name << "\";\n"; if( hdrhtml_set->image_dir==NULL ) out << obj_name << ".image_dir = \"\";\n"; else out << obj_name << ".image_dir = \"" << hdrhtml_set->image_dir << "/\";\n"; out << obj_name << ".basis = " << it->basis << ";\n"; out << obj_name << ".shared_basis = " << it->shared_basis << ";\n"; out << obj_name << ".pix_per_fstop = " << it->pix_per_fstop << ";\n"; out << obj_name << ".hist_start = " << it->hist_start << ";\n"; out << obj_name << ".hist_width = " << it->hist_width << ";\n"; out << obj_name << ".exposure = " << it->best_exp << ";\n"; out << obj_name << ".best_exp = " << it->best_exp << ";\n\n"; } } void print_image_htmlcode( ostream &out, HDRHTMLSet *hdrhtml_set, const HDRHTMLImage &it ) { std::string obj_name( "hdr_" ); obj_name.append( it.base_name ); std::ostringstream img_dir; if( hdrhtml_set->image_dir != NULL ) img_dir << hdrhtml_set->image_dir << "/"; ReplacePattern replace_list[] = { ReplacePattern( "hdr_img_width", it.width ), ReplacePattern( "hdr_img_height", it.height ), ReplacePattern( "img_dir", img_dir.str() ), ReplacePattern( "hist_width", it.hist_width ), ReplacePattern( "base_name", it.base_name ), ReplacePattern( "help_mark_pos", it.hist_width-12 ), ReplacePattern( "hdr_img_object", obj_name ), ReplacePattern( "version", hdrhtml_version ), ReplacePattern() }; create_from_template( out, hdrhtml_set->image_template, replace_list ); } void print_image_htmlcode( ostream &out, void *user_data, const char *parameter ) { HDRHTMLSet *hdrhtml_set = (HDRHTMLSet*)user_data; if( parameter != NULL ) { list::iterator it; for( it = hdrhtml_set->image_list.begin(); it != hdrhtml_set->image_list.end(); it++ ) { if( it->base_name.compare( parameter ) == 0 ) break; } if( it == hdrhtml_set->image_list.end() ) std::cerr << "Warning: image '" << parameter << "' not found\n"; print_image_htmlcode( out, hdrhtml_set, *it ); } else { list::iterator it; for( it = hdrhtml_set->image_list.begin(); it != hdrhtml_set->image_list.end(); it++ ) { print_image_htmlcode( out, hdrhtml_set, *it ); } } } void print_cf_table( ostream &out, void *user_data, const char *parameter ) { CSVTable *cf = (CSVTable*)user_data; out << "var cf = new Array(\n"; for( int b=0; b < cf->rows; b++ ) { out << " new Array("; for( int ex=0; ex < cf->columns; ex++ ) { out << ' ' << cf->data[ex][b]; if( ex != cf->columns-1 ) out << ','; } out << ')'; if( b != cf->rows-1 ) out << ','; out << "\n"; } out << ");\n"; } pfstools-2.2.0/src/hdrhtml/pfsouthdrhtml.10000664000701400070140000001406714105165614017307 0ustar rkm38rkm38.TH "pfsouthdrhtml" 1 .SH NAME pfsouthdrhtml \- Create a web page with an HDR viewer .SH SYNOPSIS \fBpfsouthdrhtml\fR [] [\fB--quality\fR <1-5>] [\fB--image-dir\fR ] [\fB--page-template\fR ] [\fB--image-template\fR ] [\fB--object-output\fR ] [\fB--html-output\fR ] .SH DESCRIPTION The command creates in the current directory an HTML web page containing multi-exposure HDR viewer. The multi-exposure viewer displays a portion of the available dynamic range with minimum contrast distortions and provides a slider control to move the dynamic range window towards brighter or darker tones. The interface is very similar to \fIpfsview\fR, which is a pfstools application for displaying HDR images. The web page employs only JavaScript and CSS opacity property and does not require Java applets or the Flash plugin. Note that because this techniques encodes 20-60 exposures using only few images, the displayed exposures may not be identical to the exposures that are shown in pfsview. For examples and more information, visit .PP \fIhttp://pfstools.sourceforge.net/hdrhtml/\fR. .PP \fI\fR specifies the file name, of the web page to be generated. If \fI\fR is missing, the file name of the first image with .html extension will be used. .PP The command can take as input several images and put them all on the same web page. For each image, its file name (from the FILE_NAME tag in the pfsstrem) without extension and a leading path will be used as a name for all JavaScript variables corresponding to that image. If the filename contains illegal characters (such as space, '-', '[', etc), these will be converted to '_'. .TP \fB--quality\fR <1-5>, \fB-q\fR <1-5> Quality of the interpolated exposures, from the worst (1) to the best (5). The default is 2, which is sufficient for most applications. Higher quality will introduce less distortions in the brightest and the darkest tones, but will also generate more images. More images means that there is more data that needs to be transferred to the web-browser, making HDR viewer less responsive. .TP \fB--image-dir\fR , \fB-d\fR Specify where to store the resulting image files. Links to images in HTML will be updated accordingly. This must be a relative path and the directory must exist. Useful to avoid clutter in the current directory. .TP \fB--page-template\fR , \fB-p\fR , \fB--image-template\fR , \fB-i\fR Replaces the template files used to generate an HTML web page. The template files contain all HTML and JaveScript code with special keywords (@keyword@) that are replaced with image specific data, such as width, height, image base name, etc. The default template files can be found in \fIINSTALL_DIR/share/pfstools/hdrhtml_default_templ/hdrhtml_*_templ.html\fR. There is an alternative template bundled with pfstools in the hdrhtml_hdrlabs_templ directory, which contains many improvements and looks much better but requires additional asset files. The example at the end of this manual shows how to use alternative template. More details on how to design own templates can be found in \fBTEMPLATE FILE FORMAT\fR below. .TP \fB--object-output\fR , \fB-o\fR Store JavaScript objects (\fIhdr_\fR) associated with each image in a separate file. This is useful if you want to script creating HTML pages. .TP \fB--html-output\fR , \fB-l\fR Store HTML code that shows HDRHTML viewer for each image in a separate file. This is useful if you want to script creating HTML pages. .SH TEMPLATE FILE FORMAT pfsouthdrhtml uses two template files \fIhdrhtml_page_templ.html\fR and \fIhdrhtml_image_templ.html\fR, located in \fIINSTALL_DIR/share/pfstools/\fR, to generate a web page with an HDR HTML viewer. The 'page' file contains the HTML of the entire web page and the 'image' file is used to paste a viewer code for a single image. You can replace one or both these templates with your own using \fB--page-template\fR and \fB--image-template\fR options. .PP Each template contains HTML code with additional keywords surrounded by @ marks (@keyword@), which are replaced with HDR HTML specific code. Most of the keywords are self explanatory, therefore only the most important are described below. .TP @hdr_img_def@ JavaScript objects that must be put in the 'body' section before any images. These define all the parameters needed to control HDR HTML viewer. .TP @cf_array_def@ Pre-computed array of opacity coefficients. The same array is used for all images that use the same quality setting. Currently only one such array could be used per web-page, so images generated with different quality setting cannot be mixed on a single web page. .TP @image_htmlcode@ or @image_htmlcode[base_name]@ Inserts HTML code of all images or a single image with the base_name (name with no file extension) specified as a parameter. This should be put where HDR HTML viewer should be located. .SH EXAMPLES .TP pfsin memorial.hdr | pfshdrhtml memorial_church Generates a web page memorial_church.html with a set of images memorial_church_*.jpg in the current directory. .TP pfsin ~/hdr_images/*.exr | pfssize --maxx 512 --maxy 512 | pfsouthdrhtml hdr_images Generate a web page with all OpenEXR images from ~/hdr_images/. The images are resized so that they are not larger than 512x512. .TP templ_dir=$INST_DIR/share/pfstools/hdrhtml_hdrlabs_templ/; pfsin img1.hdr img2.exr | pfssize -r 0.2 | pfsouthdrhtml -p ${templ_dir}/hdrhtml_page_templ.html -i ${templ_dir}/hdrhtml_image_templ.html test.html && cp -r ${templ_dir}/hdrhtml_assets ./ The commands above will use an improved template from hdrlabs.com instead of the default one. Note that this template requires html_assets directory to be copied manually to the destination directory. Replace $INST_DIR with the directory where pfstools is installed (/usr/local by default). .SH "SEE ALSO" .BR pfsin (1) .BR pfsout (1) .SH BUGS Please report bugs and comments to the discussion group http://groups.google.com/group/pfstools pfstools-2.2.0/src/hdrhtml/hdrhtml_t_b4.csv0000664000701400070140000000171514105165614017405 0ustar rkm38rkm380.0078125,0,0,0 0.0099575,0.043298,5.3555e-22,0 0.012691,0.068233,5.24e-22,0 0.016176,0.099217,3.7926e-22,0 0.020617,0.13105,1.5847e-22,0 0.026278,0.16193,9.295e-23,0 0.033493,0.19249,1.0223e-22,0 0.042689,0.22187,0.0067226,0 0.054409,0.24216,0.035882,0 0.069348,0.26569,0.063736,0 0.088388,0.2928,0.095934,0 0.11266,0.32552,0.12706,0 0.14359,0.36657,0.15933,0 0.18301,0.41682,0.19143,0 0.23326,0.48389,0.21451,0.018561 0.2973,0.56838,0.23449,0.047253 0.37893,0.65793,0.25599,0.078825 0.48297,0.74446,0.28194,0.11068 0.61557,0.83468,0.31458,0.14454 0.78458,0.91545,0.35677,0.17699 1,0.9877,0.40835,0.20735 1.2746,1,0.49517,0.21996 1.6245,1,0.58843,0.23782 2.0705,1,0.68195,0.25835 2.639,1,0.77954,0.28525 3.3636,1,0.86504,0.32097 4.2871,1,0.94003,0.36756 5.4642,1,1,0.43014 6.9644,1,1,0.51864 8.8766,1,1,0.61233 11.314,1,1,0.71075 14.42,1,1,0.80082 18.379,1,1,0.88543 23.425,1,1,0.9645 29.857,1,1,1 38.055,1,1,1 48.503,1,1,1 61.82,1,1,1 78.793,1,1,1 100.43,1,1,1 128,1,1,1 pfstools-2.2.0/src/hdrhtml/pfsouthdrhtml.cpp0000664000701400070140000001516214105165614017726 0ustar rkm38rkm38/** * @brief Create a web page with an HDR viewer * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2009 Rafal Mantiuk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Rafal Mantiuk, * * $Id: pfsouthdrhtml.cpp,v 1.5 2010/06/13 14:45:55 rafm Exp $ */ #include "hdrhtml.h" #include #include #include #include #include #include #include #include #define PROG_NAME "pfsouthdrhtml" class QuietException { }; #define min(x,y) ( (x)<(y) ? (x) : (y) ) void printHelp() { fprintf( stderr, PROG_NAME " [] [--quality <1-5>] [--image-dir ] [--page-template ] [--image-template ] [--object-output ] [--html-output ] [--verbose] [--help]\n" "See the manual page for more information.\n" ); } void generate_hdrhtml( int argc, char* argv[] ) { bool verbose = false; int quality = 2; // Parse command line parameters static struct option cmdLineOptions[] = { { "help", no_argument, NULL, 'h' }, { "verbose", no_argument, NULL, 'v' }, { "quality", required_argument, NULL, 'q' }, { "image-dir", required_argument, NULL, 'd' }, { "object-output", required_argument, NULL, 'o' }, { "html-output", required_argument, NULL, 'l' }, { "page-template", required_argument, NULL, 'p' }, { "image-template", required_argument, NULL, 'i' }, { NULL, 0, NULL, 0 } }; static const char optstring[] = "q:hvd:o:l:p:i:"; const char *page_name = NULL; const char *image_dir = NULL; const char *object_output = NULL; const char *html_output = NULL; const char *page_template = PKG_DATADIR "/hdrhtml_default_templ/hdrhtml_page_templ.html"; const char *image_template = PKG_DATADIR "/hdrhtml_default_templ/hdrhtml_image_templ.html"; int optionIndex = 0; while( 1 ) { int c = getopt_long (argc, argv, optstring, cmdLineOptions, &optionIndex); if( c == -1 ) break; switch( c ) { case 'h': printHelp(); throw QuietException(); case 'v': verbose = true; break; case 'q': quality = strtol( optarg, NULL, 10 ); if( quality < 1 || quality > 5 ) throw pfs::Exception( "The quality must be between 1 (worst) and 5 (best)." ); break; case 'd': image_dir = optarg; break; case 'o': object_output = optarg; break; case 'p': page_template = optarg; break; case 'i': image_template = optarg; break; case 'l': html_output = optarg; break; case '?': throw QuietException(); case ':': throw QuietException(); } } if( optind < argc ) { page_name = argv[optind++]; } if( optind < argc ) { throw pfs::Exception( "Too many parameters." ); } pfs::DOMIO pfsio; bool first_frame = true; Magick::InitializeMagick(""); HDRHTMLSet image_set( page_name, image_dir ); while( true ) { pfs::Frame *frame = pfsio.readFrame( stdin ); if( frame == NULL ) { break; // No more frames } // Get RGB color channels pfs::Channel *R= NULL, *B = NULL; // for clarity of the code pfs::Channel *Y; pfs::Array2DImpl G( frame->getWidth(), frame->getHeight() ); { pfs::Channel *X, *Z; frame->getXYZChannels( X, Y, Z ); if( X != NULL ) { // Has color R = X; B = Z; pfs::transformColorSpace( pfs::CS_XYZ, X, Y, Z, pfs::CS_RGB, R, &G, B ); } else { // Monochromatic Y = frame->getChannel( "Y" ); if( Y == NULL ) throw pfs::Exception( "Missing color and luminance information in one of the input frames." ); R = Y; pfs::copyArray( Y, &G ); B = Y; } } // Get base_name if needed std::string base_name; { const char *filename = frame->getTags()->getString( "FILE_NAME" ); if( filename == NULL ) { if( first_frame && page_name != NULL ) base_name = page_name; else { if( first_frame ) throw pfs::Exception( "Specify page name if FILE_NAME information in missing in the pfsstream" ); else throw pfs::Exception( "Cannot handle multiple images if FILE_NAME information in missing in the pfsstream" ); } } else { std::string tmp_str( filename ); // Remove extension int dot_pos = tmp_str.find_last_of( '.' ); if( dot_pos != std::string::npos & dot_pos > 0 ) tmp_str = tmp_str.substr( 0, dot_pos ); // Remove path int slash_pos = tmp_str.find_last_of( "/\\" ); if( slash_pos != std::string::npos ) tmp_str = tmp_str.substr( slash_pos+1, std::string::npos ); // Substitute invalid characters while( true ) { int invalid_pos = tmp_str.find_last_of( "-! #@()[]{}`." ); if( invalid_pos == std::string::npos ) break; tmp_str.replace( invalid_pos, 1, 1, '_' ); } base_name = tmp_str; } } std::cerr << "Adding image " << base_name << " to the web page\n"; image_set.add_image( frame->getWidth(), frame->getHeight(), R->getRawData(), G.getRawData(), B->getRawData(), Y->getRawData(), base_name.c_str(), quality ); pfsio.freeFrame( frame ); first_frame = false; } image_set.generate_webpage( page_template, image_template, object_output, html_output ); } int main( int argc, char* argv[] ) { try { generate_hdrhtml( argc, argv ); } catch( pfs::Exception ex ) { fprintf( stderr, PROG_NAME " error: %s\n", ex.getMessage() ); return EXIT_FAILURE; } catch( QuietException ex ) { return EXIT_FAILURE; } return EXIT_SUCCESS; } pfstools-2.2.0/src/hdrhtml/CMakeLists.txt0000664000701400070140000000163014105165614017052 0ustar rkm38rkm38include_directories ("${PROJECT_BINARY_DIR}/" "${PROJECT_SOURCE_DIR}/src/pfs" ${ImageMagick_INCLUDE_DIRS}) link_directories("${PROJECT_SOURCE_DIR}/src/pfs") add_executable(pfsouthdrhtml pfsouthdrhtml.cpp hdrhtml.cpp) target_link_libraries(pfsouthdrhtml pfs ${ImageMagick_LIBRARIES}) set_target_properties(pfsouthdrhtml PROPERTIES COMPILE_FLAGS "-DMAGICKCORE_HDRI_ENABLE=0 -DMAGICKCORE_QUANTUM_DEPTH=16") install (TARGETS pfsouthdrhtml DESTINATION bin) install (FILES pfsouthdrhtml.1 DESTINATION ${MAN_DIR}) set(DATAFILES hdrhtml_c_b2.csv hdrhtml_t_b2.csv hdrhtml_c_b3.csv hdrhtml_t_b3.csv hdrhtml_c_b4.csv hdrhtml_t_b4.csv hdrhtml_c_b5.csv hdrhtml_t_b5.csv) foreach(SRC ${DATAFILES}) install (FILES ${SRC} DESTINATION ${PKG_DATADIR}) endforeach(SRC) install( DIRECTORY "hdrhtml_default_templ" DESTINATION ${PKG_DATADIR} ) install( DIRECTORY "hdrhtml_hdrlabs_templ" DESTINATION ${PKG_DATADIR} ) pfstools-2.2.0/src/hdrhtml/hdrhtml_c_b4.csv0000664000701400070140000000171714105165614017366 0ustar rkm38rkm381.000000, 0.000000, 0.000000, 0.000000 1.000000, 0.107779, 0.000000, 0.000000 1.000000, 0.225141, 0.000000, 0.000000 1.000000, 0.346138, 0.000000, 0.000000 1.000000, 0.468157, 0.000000, 0.000000 1.000000, 0.588770, 0.000000, 0.000000 1.000000, 0.705845, 0.000000, 0.000000 1.000000, 0.817590, 0.000000, 0.000000 1.000000, 0.900184, 0.035347, 0.000000 1.000000, 0.949359, 0.116636, 0.000000 1.000000, 0.986104, 0.215020, 0.000000 0.939311, 1.000000, 0.330480, 0.000000 0.984719, 1.000000, 0.453535, 0.000000 0.983375, 1.000000, 0.575528, 0.000000 0.982920, 1.000000, 0.694216, 0.000000 0.981837, 1.000000, 0.807719, 0.000000 0.973294, 1.000000, 0.894052, 0.033054 1.000000, 0.944328, 0.946046, 0.117524 1.000000, 0.613026, 0.985231, 0.220520 0.824505, 0.830313, 1.000000, 0.340765 0.822916, 0.826307, 1.000000, 0.471029 0.828063, 0.831705, 1.000000, 0.600446 0.803956, 0.831663, 1.000000, 0.726575 0.734713, 0.805514, 1.000000, 0.847311 0.109163, 0.305546, 0.885145, 0.956582 pfstools-2.2.0/src/hdrhtml/hdrhtml_c_b3.csv0000664000701400070140000000132514105165614017360 0ustar rkm38rkm381.000000, 0.004753, 0.000806 1.000000, 0.077796, 0.000000 1.000000, 0.153789, 0.000000 1.000000, 0.232821, 0.000000 1.000000, 0.313796, 0.000000 1.000000, 0.395649, 0.000000 1.000000, 0.477428, 0.000000 1.000000, 0.558251, 0.000000 1.000000, 0.637553, 0.000000 1.000000, 0.714245, 0.000000 1.000000, 0.787620, 0.000000 1.000000, 0.857177, 0.000000 1.000000, 0.897915, 0.045207 1.000000, 0.931794, 0.101116 1.000000, 0.960051, 0.165419 0.828804, 1.000000, 0.230594 0.824994, 1.000000, 0.314135 0.815345, 1.000000, 0.399492 0.807275, 1.000000, 0.484852 0.792214, 1.000000, 0.569285 0.820934, 1.000000, 0.651947 0.888018, 1.000000, 0.732082 0.932047, 1.000000, 0.809024 1.000000, 0.930768, 0.886016 0.674939, 0.597552, 0.947762 pfstools-2.2.0/src/hdrhtml/hdrhtml_default_templ/0002775000701400070140000000000014105165614020663 5ustar rkm38rkm38pfstools-2.2.0/src/hdrhtml/hdrhtml_default_templ/hdrhtml_page_templ.html0000664000701400070140000002220414105165614025406 0ustar rkm38rkm38 @title@ @image_htmlcode@ pfstools-2.2.0/src/hdrhtml/hdrhtml_default_templ/hdrhtml_image_templ.html0000664000701400070140000000267414105165614025565 0ustar rkm38rkm38
1
?
pfstools-2.2.0/src/hdrhtml/hdrhtml_t_b2.csv0000664000701400070140000000100014105165614017366 0ustar rkm38rkm380.0078125,0 0.0099575,0 0.012691,0 0.016176,0.034072 0.020617,0.072078 0.026278,0.11268 0.033493,0.15532 0.042689,0.20165 0.054409,0.24921 0.069348,0.29912 0.088388,0.35227 0.11266,0.40442 0.14359,0.45943 0.18301,0.51602 0.23326,0.57033 0.2973,0.62743 0.37893,0.68417 0.48297,0.73784 0.61557,0.79391 0.78458,0.84724 1,0.89681 1.2746,0.93968 1.6245,0.97889 2.0705,1 2.639,1 3.3636,1 4.2871,1 5.4642,1 6.9644,1 8.8766,1 11.314,1 14.42,1 18.379,1 23.425,1 29.857,1 38.055,1 48.503,1 61.82,1 78.793,1 100.43,1 128,1 pfstools-2.2.0/src/hdrhtml/hdrhtml_t_b5.csv0000664000701400070140000000230614105165614017403 0ustar rkm38rkm380.0078125,0,0,0,0 0.0099575,0.046825,0,0,0 0.012691,0.071986,1.3855e-21,0,1.3404e-21 0.016176,0.1037,1.8408e-21,0,4.4849e-21 0.020617,0.13616,8.0301e-22,0,3.2708e-21 0.026278,0.16633,0.007471,0,3.1119e-21 0.033493,0.1947,0.032949,0,3.1648e-21 0.042689,0.22529,0.058851,0,3.0057e-21 0.054409,0.253,0.094366,0,2.6424e-21 0.069348,0.2829,0.12843,0,1.5325e-22 0.088388,0.31506,0.16299,0.0037173,0 0.11266,0.3466,0.18855,0.03263,0 0.14359,0.38407,0.21578,0.061727,0 0.18301,0.42874,0.2435,0.093971,0 0.23326,0.48464,0.26963,0.12885,0 0.2973,0.55177,0.29885,0.16477,0.0031392 0.37893,0.64391,0.32942,0.19135,0.033828 0.48297,0.73599,0.36529,0.21705,0.063574 0.61557,0.83329,0.40955,0.24375,0.098502 0.78458,0.92083,0.46476,0.27055,0.13365 1,0.99702,0.53092,0.2982,0.16758 1.2746,1,0.63416,0.32596,0.19102 1.6245,1,0.73977,0.35658,0.22051 2.0705,1,0.83777,0.39756,0.24813 2.639,1,0.92953,0.45196,0.27761 3.3636,1,1,0.52151,0.30659 4.2871,1,1,0.62457,0.33166 5.4642,1,1,0.73619,0.36229 6.9644,1,1,0.83644,0.40322 8.8766,1,1,0.92453,0.45727 11.314,1,1,1,0.52709 14.42,1,1,1,0.62724 18.379,1,1,1,0.73253 23.425,1,1,1,0.83977 29.857,1,1,1,0.92215 38.055,1,1,1,1 48.503,1,1,1,1 61.82,1,1,1,1 78.793,1,1,1,1 100.43,1,1,1,1 128,1,1,1,1 pfstools-2.2.0/src/hdrhtml/hdrhtml_t_b3.csv0000664000701400070140000000144314105165614017402 0ustar rkm38rkm380.0078125,0,0 0.0099575,0.04108,3.708e-20 0.012691,0.047823,5.4321e-20 0.016176,0.065391,5.9717e-20 0.020617,0.083157,5.6894e-20 0.026278,0.10398,6.3196e-20 0.033493,0.12791,6.3889e-20 0.042689,0.1563,6.041e-20 0.054409,0.19097,5.6943e-20 0.069348,0.23139,5.4626e-20 0.088388,0.2763,0.010773 0.11266,0.32933,0.021906 0.14359,0.39697,0.033063 0.18301,0.46994,0.045095 0.23326,0.54158,0.061894 0.2973,0.61753,0.082825 0.37893,0.69394,0.10713 0.48297,0.76558,0.13532 0.61557,0.83936,0.1691 0.78458,0.90635,0.20897 1,0.96558,0.25482 1.2746,1,0.30533 1.6245,1,0.37926 2.0705,1,0.45322 2.639,1,0.53436 3.3636,1,0.61317 4.2871,1,0.69167 5.4642,1,0.77153 6.9644,1,0.84026 8.8766,1,0.90374 11.314,1,0.96351 14.42,1,1 18.379,1,1 23.425,1,1 29.857,1,1 38.055,1,1 48.503,1,1 61.82,1,1 78.793,1,1 100.43,1,1 128,1,1 pfstools-2.2.0/src/hdrhtml/hdrhtml_c_b2.csv0000664000701400070140000000073314105165614017361 0ustar rkm38rkm381.000000, 0.000000 1.000000, 0.029169 1.000000, 0.063614 1.000000, 0.100425 1.000000, 0.139501 1.000000, 0.180528 1.000000, 0.223219 1.000000, 0.267228 1.000000, 0.312396 1.000000, 0.358571 1.000000, 0.405153 1.000000, 0.451895 1.000000, 0.498571 1.000000, 0.544955 1.000000, 0.590843 1.000000, 0.636067 1.000000, 0.680764 1.000000, 0.724061 1.000000, 0.766052 1.000000, 0.806596 1.000000, 0.845547 1.000000, 0.882793 1.000000, 0.918213 1.000000, 0.951773 0.985495, 0.983571 pfstools-2.2.0/src/hdrhtml/hdrhtml.h0000664000701400070140000000414714105165614016133 0ustar rkm38rkm38/** * @brief Create a web page with an HDR viewer * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2009 Rafal Mantiuk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Rafal Mantiuk, * * $Id: hdrhtml.h,v 1.3 2009/02/07 07:08:47 rafm Exp $ */ #ifndef HDRHTML_H #define HDRHTML_H #include #include class HDRHTMLImage { public: std::string base_name; int width, height; int f8_stops; int f_step_res; int basis; int shared_basis; int pix_per_fstop; float hist_start; int hist_width; float exposure; float best_exp; HDRHTMLImage( const char *base_name, int width, int height ) : base_name( base_name ), width( width ), height( height ) { } }; class HDRHTMLSet { const char *page_name; public: const char *image_dir; const char *image_template; std::list image_list; HDRHTMLSet( const char *page_name, const char *image_dir = NULL ) : page_name( page_name ), image_dir( image_dir ) { } void add_image( int width, int height, float *R, float *G, float *B, float *Y, const char *base_name, int quality ); void generate_webpage( const char *page_template, const char *image_template, const char *object_output = NULL, const char *html_output = NULL); }; #endif pfstools-2.2.0/src/hdrhtml/hdrhtml_c_b5.csv0000664000701400070140000000231114105165614017356 0ustar rkm38rkm381.000000, 0.004941, 0.000000, 0.000000, 0.000000 1.000000, 0.157191, 0.000000, 0.000000, 0.000000 1.000000, 0.316501, 0.000000, 0.000000, 0.000000 1.000000, 0.477850, 0.000000, 0.000000, 0.000000 1.000000, 0.636794, 0.000000, 0.000000, 0.000000 1.000000, 0.789464, 0.000000, 0.000000, 0.000000 1.000000, 0.891700, 0.056136, 0.000000, 0.000000 1.000000, 0.954463, 0.164808, 0.000000, 0.000000 0.995961, 1.000000, 0.298554, 0.000000, 0.000000 0.992357, 1.000000, 0.460134, 0.000000, 0.000000 0.992921, 1.000000, 0.619440, 0.000000, 0.000000 0.993587, 1.000000, 0.772463, 0.000000, 0.000000 0.994573, 1.000000, 0.874541, 0.057581, 0.000000 1.000000, 0.951017, 0.939871, 0.167569, 0.000000 1.000000, 0.328929, 0.988487, 0.302869, 0.000000 0.844179, 0.878493, 1.000000, 0.459841, 0.000000 0.804767, 0.851381, 1.000000, 0.623223, 0.000000 0.793615, 0.861199, 1.000000, 0.780415, 0.000000 0.750195, 0.823210, 1.000000, 0.887745, 0.056321 0.702168, 0.777604, 1.000000, 0.951909, 0.171740 0.563097, 0.610780, 0.754127, 1.000000, 0.312939 0.538779, 0.594761, 0.695918, 1.000000, 0.483468 0.413003, 0.469412, 0.564120, 1.000000, 0.652870 0.195722, 0.274036, 0.417979, 1.000000, 0.816901 0.095966, 0.000000, 0.000000, 0.911089, 0.966808 pfstools-2.2.0/src/pfsglview/0002775000701400070140000000000014105165614014660 5ustar rkm38rkm38pfstools-2.2.0/src/pfsglview/histogram.h0000664000701400070140000000270114105165614017024 0ustar rkm38rkm38#ifndef HISTOGRAM_H #define HISTOGRAM_H #include "rmglwin.h" enum SelectedBar { NONE, LEFT_BAR, RIGHT_BAR, WHOLE_SLIDER }; class Histogram : public RMGLWin { private: int xPos, yPos; int width, height; int* frequencyValues; GLfloat* backgroundColor; float frequencyMax; float sliderPosMin, sliderPosMax; // luminance volues for start and end of slider float lumMin, lumMax; float logLumMin, logLumMax; SelectedBar selectionState; private: void drawBackground(); void drawScale(); void drawStatistic(); void drawSlider(); public: Histogram( int xPos, int yPos, int width, int height); ~Histogram(); void computeFrequency(const pfs::Array2D *image); void drawHistogram(); float getHighFrequency() const; float getMaxFrequency() const; int getWidth( void); int getHeight( void); int getBackgroundWidth(void); int getBackgroundHeight(void); float getLumMin(); float getLumMax(); float pos2lum( float pos); float lum2pos( float lum); void computeLumRange( float& min, float& max); void processSliderSelection(int xCoord, int yCoord); int setSliderSelectionState(SelectedBar newState); SelectedBar getSliderSelectionState(); void setSliderPosMin( float pos); float getSliderPosMin(void); void setSliderPosMax( float pos); float getSliderPosMax(void); void setSliderPosMinMax( float min, float max); void resetFrequencyMax(void); void redraw(void); }; #endif pfstools-2.2.0/src/pfsglview/winstat.h0000664000701400070140000000122514105165614016520 0ustar rkm38rkm38 #include "rmglwin.h" class WinStat : public RMGLWin { private: int pixelX; int pixelY; int colR, colG, colB; float lumMax; const char* mappingMode; int frameNo; const char* channel; int rawPosX, rawPosY; float rawX, rawY, rawZ; bool bZoom; public: //WinStat( int posX, int posY, int width, int height); WinStat(); ~WinStat(); void redraw(void); void setPixelData( int xx, int yy, int r, int g, int b); void setMaxFreq(float val); void setMapping(const char*); void setFrameNo(int no); void setChannel(const char* ch) ; void setRawData(int x, int y, float X, float Y, float Z); void setBZoom(bool bb); }; pfstools-2.2.0/src/pfsglview/m_on_screen_display.h0000664000701400070140000000326614105165614021052 0ustar rkm38rkm38/** * @brief PFS library - additional utilities * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2006 Radoslaw Mantiuk * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Radoslaw Mantiuk, * @author Oliver Barth */ #ifndef M_ON_SCREEN_DISPLAY_H #define M_ON_SCREEN_DISPLAY_H #include #include "module.h" class M_OnScreenDisplay : public Module { private: float start_y ; int max_row_length ; int row_count ; bool hasBorder ; std::string* text ; public: M_OnScreenDisplay(std::string* s, int row_count); ~M_OnScreenDisplay(); void redraw(void); void println(std::string s) ; int getDesiredWinHeight() ; int getDesiredWinWidth() ; void setHasBorder(bool has_border) ; void setBackgroundColor(float _r, float _g, float _b, float _a) ; }; #endif pfstools-2.2.0/src/pfsglview/pfsglview.10000664000701400070140000000632014105165614016747 0ustar rkm38rkm38.TH "pfsglview" 1 .SH NAME pfsglview \- Viewer for high-dynamic range images in pfs format .SH SYNOPSIS .B pfsglview [--h] [--v] .SH DESCRIPTION pfsview is a OpenGL/GLUT application for viewing high-dynamic range images. It expects pfs stream on the standard input and displays the frames in that stream one by one. .SH DYNAMIC RANGE WINDOW To show high-dynamic range data on a low-dynamic range monitor, pfsglview uses concept of a dynamic range window. The dynamic range window is the highest and lowest value that should be mapped to black and white pixel. Values above or below the window are clipped (see clipping methods below). The dynamic range window is displayed in pfsglview as a blue area on the dynamic range scale (second toolbox from the top). The window can be moved, shrunk and expended using a mouse or a keyboard. .SH ZOOMING AND PANNING To zoom image, the mouse can be dragged in vertical direction with the left button pressed. Pressing [space] button or pressing left mouse button above the statistic window (left-bottom corner) changes from zooming to panning modes and vice versa. To pan image, the mouse can be dragged in vertical and horizontal directions with the left button pressed. .SH POPUP MENU OPTIONS .TP .B Zoom reset Set default zoom parameters. .TP .B Zoom in Increase image (mouse dragging with left button pressed). .TP .B Zoom out Decrease image (mouse dragging with left button pressed). .TP .B Increase exposure Move dynamic range window into higher luminance values. .TP .B Decrease exposure Move dynamic range window into lover luminance values. .TP .B Extend dynamic range Extend dynamic range window. .TP .B Shrink dynamic range Shrink dynamic range window .TP .B Low dynamic range Set dynamic range window to <-1,1> range (log scale). .TP .B Fit to dynamic range Set dynamic range windo to minimum and maximum luminance of a given image. .TP .B Choose channel Change image data channel. .TP .B Mapping method Change mapping method (see below for details). .TP .B Next frame Display next image from the pipe. .TP .B Previous frame Display previous image from the pipe. .TP .B Histogram Switch on/off histogram window. .TP .B Info Switch on/off info window. .TP .B Save&Quit Send the visible LDR image (8-bits) to stdout and quit pfsglview. .SH MAPPING METHODS High-dynamic range data are usually better visualized using non-linear scale, for example a logarithmic or a power function. pfsglview offers several such scales, shown in \fIpopup\fR menu. Gray-scale values for each mapping method are computed by the formulas: \fBLINEAR\fR: y = (x-min)/(max-min) \fBGAMMA\fR: y = [ (x-min)/(max-min) ]^gamma \fBLOGARITHMIC\fR: y = (log10(x)-log10(min))/(log10(max)-log10(min)) where \fIy\fR is the gray-scale value after mapping, \fIx\fR is an input HDR value, \fImin\fR and \fImax\fR are lower and upper bounds of the dynamic range window. .SH EXAMPLES .TP pfsin memorial.hdr | pfsglview See the memorial image. .TP pfsin memorial.hdr | pfsglview | pfsout memorial.jpg See the memorial image and save the clipped (8-bits) version to memorial.jpg (\fISave&Quit\fR option from popup menu should be activated). .SH "SEE ALSO" .BR pfsin (1) Please report bugs and comments to Radoslaw Mantiuk . pfstools-2.2.0/src/pfsglview/m_on_screen_display.cpp0000664000701400070140000000773114105165614021406 0ustar rkm38rkm38/** * @brief PFS library - additional utilities * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2006 Radoslaw Mantiuk * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Radoslaw Mantiuk, * */ #include #include #include "m_on_screen_display.h" #define FONT GLUT_BITMAP_8_BY_13 #define FONT_WIDTH 8 #define FONT_HEIGHT 13 #define ROW_SPACING 2 #define BORDER_OUTER 5 #define BORDER_INNER 20 #define BORDER ((hasBorder)?(BORDER_OUTER + BORDER_INNER):0) M_OnScreenDisplay::M_OnScreenDisplay(std::string* s, int row_count) : Module() { isVisible = GL_FALSE; hasBorder = true ; text = s ; winBackgroundColor = new float[4]; for(int i=0; i<3; i++){ winBackgroundColor[i] = 0.2f; } winBackgroundColor[3] = 0.95f; this->row_count = row_count ; max_row_length = 0 ; for(int i=0; i < row_count; i++){ if(max_row_length < text[i].length()) max_row_length = text[i].length() ; } } M_OnScreenDisplay::~M_OnScreenDisplay() { } void M_OnScreenDisplay::redraw(void) { if( Module::redrawStart()) return; float offset_y = ((height-2*BORDER)/(FONT_HEIGHT + ROW_SPACING)-row_count)/2.0 ; offset_y = offset_y > 0 ? offset_y : 0; start_y = offset_y ; glColor3f(0.5f, 1.0f, 0.5f); if(hasBorder){ glBegin(GL_LINE_STRIP); glVertex3f((BORDER_OUTER-1) , BORDER_OUTER , -0.5f); glVertex3f(BORDER_OUTER + width-(BORDER_OUTER-1)-BORDER_OUTER, BORDER_OUTER , -0.5f); glVertex3f(BORDER_OUTER + width-(BORDER_OUTER-1)-BORDER_OUTER, BORDER_OUTER + height-(BORDER_OUTER-1)-BORDER_OUTER, -0.5f); glVertex3f(BORDER_OUTER , BORDER_OUTER + height-(BORDER_OUTER-1)-BORDER_OUTER, -0.5f); glVertex3f(BORDER_OUTER , (BORDER_OUTER-1) , -0.5f); glEnd(); } glEnable(GL_SCISSOR_TEST); glScissor( pos_x + BORDER, pos_y + BORDER, width - 2 * BORDER, height - 2 * BORDER); for(int i=0; i < row_count; i++){ println(text[i]) ; } glDisable( GL_SCISSOR_TEST); Module::redrawEnd(); } void M_OnScreenDisplay::println(std::string s){ int offset_x = (width - 2 * BORDER - (max_row_length) * FONT_WIDTH)/2 ; int offset_y = height - start_y * (FONT_HEIGHT + ROW_SPACING) -FONT_HEIGHT + ROW_SPACING ; offset_x = offset_x > 0 ? offset_x : 0; offset_x += BORDER ; offset_y -= BORDER ; glRasterPos2f(offset_x, offset_y); for (int i = 0; i < (int) s.length(); i++){ glutBitmapCharacter(FONT, s[i]); } start_y++ ; } int M_OnScreenDisplay::getDesiredWinWidth(){ return (max_row_length) * FONT_WIDTH + 2 * BORDER ; } int M_OnScreenDisplay::getDesiredWinHeight(){ return (FONT_HEIGHT + ROW_SPACING) * row_count + 2 * BORDER; } void M_OnScreenDisplay::setHasBorder(bool _hasBorder){ hasBorder = _hasBorder ; } void M_OnScreenDisplay::setBackgroundColor(float _r, float _g, float _b, float _a){ winBackgroundColor[0] = _r; winBackgroundColor[1] = _g; winBackgroundColor[2] = _b; winBackgroundColor[3] = _a; } pfstools-2.2.0/src/pfsglview/module.cpp0000664000701400070140000000761414105165614016657 0ustar rkm38rkm38/** * @brief PFS library - additional utilities * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2006 Radoslaw Mantiuk * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Radoslaw Mantiuk, * @author Oliver Barth */ #include #include "module.h" Module::Module() { isVisible = GL_TRUE; pos_x = 10; pos_y = 10; width = 100; height = 40; // background color winBackgroundColor = new float[4]; for(int i=0; i<3; i++) winBackgroundColor[i] = 1.0f; winBackgroundColor[3] = 0.8f; } Module::~Module() { delete [] winBackgroundColor; } void Module::drawBackground(void) { if( isVisible == GL_FALSE) return; // draw background glEnable(GL_BLEND); glColor4fv(winBackgroundColor); glRecti( 0, 0, width, height); glDisable(GL_BLEND); } int Module::redrawStart(void) { if( isVisible == GL_FALSE) return 1; glGetIntegerv( GL_VIEWPORT, param); // remember old vieport glMatrixMode(GL_MODELVIEW); glPushMatrix(); //glLoadIdentity(); glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); glViewport( pos_x, pos_y, width, height); glOrtho( 0, width, 0, height, -1.0f, 1.0f); drawBackground(); return 0; } void Module::redrawEnd(void) { if( isVisible == GL_FALSE) return; glMatrixMode(GL_PROJECTION); glPopMatrix(); glViewport( param[0], param[1], param[2], param[3]); // restore viewport glMatrixMode(GL_MODELVIEW); glPopMatrix(); } void Module::setPosition( int _x, int _y) { pos_x = _x; pos_y = _y; } void Module::setSize( int _width, int _height) { width = _width; height = _height; } int Module::getWidth() { return width ; } int Module::getHeight() { return height ; } void Module::setWidth(int _width){ width = _width; } void Module::setHeight(int _height){ height = _height; } void Module::setVisible(bool _isVisible) { isVisible = _isVisible; } bool Module::getVisible(void) { return isVisible; } // not used anymore /* int RMGLWin::processSelection(int xCoord, int yCoord) { if( isVisible == GL_FALSE) return 1; redrawStart(); // Hits counter and viewport martix GLint hits, viewp[4]; // Get actual viewport glGetIntegerv(GL_VIEWPORT, viewp); #define BUFFER_SIZE 64 // Table for selection buffer data GLuint selectionBuffer[BUFFER_SIZE]; // Prepare selection buffer glSelectBuffer(BUFFER_SIZE, selectionBuffer); // Change rendering mode glRenderMode(GL_SELECT); // Initializes the Name Stack glInitNames(); // Push 0 (at least one entry) Onto the Stack glPushName(0); // Set new projection matrix as a box around xPos, yPos glLoadIdentity(); int hh = glutGet(GLUT_WINDOW_HEIGHT); // Picking matrix at position xCoord, windowSize - yCoord (fliped window Y axis) // and size of 4 units in depth gluPickMatrix(xCoord, hh - yCoord, 4, 4, viewp); glOrtho(0.0f, viewp[2], 0.0f, viewp[3], -10.0f, 10.0f); // last 1.0 -1.0 it's enough drawBackground(); // draw only picked parts int ret = 0; hits = glRenderMode(GL_RENDER); if(hits > 0) { ret = 1; } redrawEnd(); return ret; } */ pfstools-2.2.0/src/pfsglview/m_status.h0000664000701400070140000000413214105165614016666 0ustar rkm38rkm38/** * @brief PFS library - additional utilities * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2006 Radoslaw Mantiuk * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Radoslaw Mantiuk, * @author Oliver Barth */ #ifndef M_STATUS_H #define M_STATUS_H #include "module.h" class SC_Status : public Module { private: int positionX ; int positionY ; int valueR, valueG, valueB ; int rawPositionX ; int rawPositionY ; float rawValueX, rawValueY, rawValueZ ; const char* mappingMode; const char* navigationMode; const char* channel; float lumMax; int frameNo; float zoom_scale ; public: SC_Status(); ~SC_Status(); // ************************************ // inherited methods // ************************************ void redraw(void); // ************************************ void setPixelData( int x, int y, int r, int g, int b); void setRawData(int x, int y, float X, float Y, float Z); void setMaxFreq(float _max_frequency); void setMapping(const char* _mappingMode); void setNavMode(const char* _navigationMode) ; void setFrameNo(int _frameNo); void setChannel(const char* _channel) ; void setZoomScale(float zoom_scale) ; }; #endif pfstools-2.2.0/src/pfsglview/CMakeLists.txt0000664000701400070140000000132314105165614017415 0ustar rkm38rkm38 include_directories ("${PROJECT_BINARY_DIR}/" "${PROJECT_SOURCE_DIR}/src/pfs" ${OPENGL_INCLUDE_DIRS} ${GLUT_INCLUDE_DIRS}) if( NOT HAS_GETOPT ) include_directories ("${GETOPT_INCLUDE}") endif( NOT HAS_GETOPT ) if (OPENMP_FOUND) # set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}") set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}") endif() add_executable(pfsglview pfsglview.cpp picture_io.cpp module.cpp m_histogram.cpp m_status.cpp m_on_screen_display.cpp) # TODO: Use ${GLUT_LIBRARY} instead. target_link_libraries(pfsglview ${OPENGL_LIBRARIES} ${GLUT_glut_LIBRARY} pfs) install (TARGETS pfsglview DESTINATION bin) install (FILES pfsglview.1 DESTINATION ${MAN_DIR}) pfstools-2.2.0/src/pfsglview/histogram.cpp0000664000701400070140000002644414105165614017371 0ustar rkm38rkm38/** * @brief PFS library - additional utilities * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2006 Radoslaw Mantiuk * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * */ #include #include #include #include #include "glenv.h" #include "pfs.h" #include "rmglwin.h" #include "histogram.h" #define SIDE_BAR 5 #define BOTTOM_BAR 14 #define SCALE_BAR 10 #define MIN_LUM 0.000001f #define MAX_LUM 100000000.0f /** * @param xPos starting x position of histogram * @param yPos starting y position of histogram * @param width width of histogram (in points/pixels), the number of bins is equal to this value * @param height height of histogram (in points/pixels) */ Histogram::Histogram( int xPos, int yPos, int width, int height) : RMGLWin() { this->xPos = xPos; this->yPos = yPos; this->width = width - SIDE_BAR*2; this->height = height - BOTTOM_BAR; // background color backgroundColor = new float[3]; for(int i=0; i<3; i++) backgroundColor[i] = 1.0f; frequencyMax = -1; sliderPosMin = 0; sliderPosMax = 0; this->lumMin = MIN_LUM; this->lumMax = MAX_LUM; logLumMin = log10(lumMin); logLumMax = log10(lumMax); selectionState = NONE; } Histogram::~Histogram() { delete [] frequencyValues; delete [] backgroundColor; } /** Redraw window */ void Histogram::redraw(void) { RMGLWin::redrawStart(); drawHistogram(); drawScale(); drawStatistic(); drawSlider(); RMGLWin::redrawEnd(); } /** Draws histogram. */ void Histogram::drawHistogram() { if(frequencyValues == NULL) return; if( frequencyMax == -1) frequencyMax = getHighFrequency(); // not max but high to remove very large picks (e.g. near very light bins) int offsetX = SIDE_BAR; int offsetY = height / 4; glColor3f(1.0f, 0.0f, 0.0f); float barHeight = 0; for(int lum = 0; lum< width; lum++) { float hh = frequencyValues[lum] / frequencyMax; if( hh > 1) hh = 1; barHeight = (float)height * hh; if( barHeight > 0 && barHeight < 1.0) barHeight = 1.0; if(barHeight > 0) glRectf(offsetX + lum, offsetY, offsetX+lum + 1, offsetY + barHeight); } } /** Draws background */ void Histogram::drawBackground() { glColor3fv(backgroundColor); glRectf( xPos/2, yPos, width + SIDE_BAR*2 + xPos/2, yPos - height - BOTTOM_BAR - SCALE_BAR); } /** Draws scale */ void Histogram::drawScale() { glColor3f(0.1f, 0.1f, 0.1f); float pos; int offsetX = SIDE_BAR; int offsetY = height / 4;//yPos - height; int k1 = 0; for( int i = (int)logLumMin; i <= (int)logLumMax; i++) { pos = lum2pos( pow(10, (float)i)) * (float)width; glRectf( offsetX + pos, offsetY, offsetX + pos + 1, offsetY + height/2); if( i < 0) { glRasterPos2f( offsetX + pos - 5, offsetY - 10); glutBitmapCharacter(GLUT_BITMAP_HELVETICA_10, '-'); glutBitmapCharacter(GLUT_BITMAP_HELVETICA_10, '6' - (k1++)); } else { glRasterPos2f( offsetX + pos - 2, offsetY - 10); glutBitmapCharacter(GLUT_BITMAP_HELVETICA_10, '0' + i); } } glRectf( offsetX, offsetY, offsetX+pos + 1, offsetY-1); } /** Draws statistic of a current image. */ void Histogram::drawStatistic() { int offsetX = winWidth - 130; int offsetY = height ; glRasterPos2f( offsetX, offsetY); char ss[200]; sprintf( ss, "logLum.:<%.3f, %.3f>" , log10(pos2lum(sliderPosMin)), log10(pos2lum(sliderPosMax))); int len = (int) strlen(ss); for (int i = 0; i < len; i++) { glutBitmapCharacter(GLUT_BITMAP_HELVETICA_10, ss[i]); } } /** Draws slider */ void Histogram::drawSlider() { float minValue = width * sliderPosMin; float maxValue = width * sliderPosMax; int sliderHeight = height; int offsetX = SIDE_BAR; int offsetY = height + height/4 ; glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA); glColor4f(0.2f, 0.2f, 0.5f, 0.3f); glLoadName(WHOLE_SLIDER); glBegin(GL_QUADS); glVertex3f(offsetX + minValue, offsetY, -0.5f); glVertex3f(offsetX + minValue, offsetY - sliderHeight, -0.5f); glVertex3f(offsetX + maxValue, offsetY - sliderHeight, -0.5f); glVertex3f(offsetX + maxValue, offsetY, -0.5f); glEnd(); glColor4f(0.7f, 0.7f, 0.8f, 0.5f); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); glTranslatef(offsetX + minValue, offsetY - sliderHeight/2, 0.0f); glRotatef(-90.0f, 0.0f, 1.0f, 0.0f); glLoadName(LEFT_BAR); glutSolidCone(sliderHeight/2, sliderHeight/3.0, 2, 2); glPopMatrix(); glPushMatrix(); glLoadIdentity(); glTranslatef(offsetX + maxValue, offsetY - sliderHeight/2, 0.0f); glRotatef(90.0f, 0.0f, 1.0f, 0.0f); glLoadName(RIGHT_BAR); glutSolidCone(sliderHeight/2, sliderHeight/3.0, 2, 2); glPopMatrix(); glDisable(GL_BLEND); } /** Creates histogram. Histogram will have 'width' bins. */ void Histogram::computeFrequency(const pfs::Array2D *image) { const int size = image->getRows()*image->getCols(); frequencyValues = new int[width]; for( int i = 0; i < width; i++ ) frequencyValues[i] = 0; for( int i = 0; i < size; i++ ) { float val = (*image)(i); if( val <= 0 || val > lumMax) { //fprintf( stderr, "WARNING: pixel luminance out of range (Histogram::computeFrequency())\n"); continue; } val = log10(val); // Calculate frequency value index in table: <0, with> int index = (int)((val - logLumMin) / (logLumMax - logLumMin) * (float)width); if( index > width || index < 0 ) continue; if( index == width ) index = width - 1; // Increase the counter of this value frequencyValues[index] += 1; } } /** Computes the maximum frequency in an image based on histogram data. */ float Histogram::getMaxFrequency() const { int maxFreq = -1; for( int i = 0; i < width; i++ ) { if( frequencyValues[i] > maxFreq ) { maxFreq = frequencyValues[i]; } } return maxFreq; } /** Returns frequence of the bin = max_bin - PERCENTIL * number_of_bins. Very light and very dark bins * are not taken into consideration in this computations. It allows to skip very dark (black) and/or * very light (white) pixels which can cover large areas of an image and lower the height of histogram for remaining * pixels (undesirable situation: flat histogram with one large pick for very light bin). */ float Histogram::getHighFrequency() const { std::vector vec; for( int i = 0; i < width; i++ ) { vec.push_back((float)frequencyValues[i]); } std::sort(vec.begin(), vec.end()); #define PERCENTIL 0.99 float highFreq = vec[(int)((float)vec.size() * PERCENTIL)]; // returns high frequency only if max frequency is 3 or more times higher float maxFreq = getMaxFrequency(); if( (3*highFreq) > maxFreq) return maxFreq; else return highFreq; } /** Converts horizontal position in the histogram into luminance value. The position must be in range <0,1>. */ float Histogram::pos2lum( float pos) { if( pos < 0) pos = 0; if( pos > 1) pos = 1; return pow( 10, pos * (logLumMax - logLumMin) + logLumMin); } /** Converts luminance value into position in range <0,1>. */ float Histogram::lum2pos( float lum) { if( lum < lumMin) lum = lumMin; if( lum > lumMax) lum = lumMax; return (log10(lum) - logLumMin) / (logLumMax - logLumMin); } /** Computes initial position of the slider. The calculation are based on the shape of histogram. */ #define FREQUENCY_EDGE 0.2 void Histogram::computeLumRange( float& min, float& max) { float maxFreq = getHighFrequency(); float freq; for( int i = 0; i < width; i++) { freq = frequencyValues[i] / maxFreq; if( freq > FREQUENCY_EDGE && freq < 1.0){ min = pos2lum((float)i / (float)width); break; } } for( int i = (width-1); i >=0; i--) { freq = frequencyValues[i] / maxFreq; if( freq > FREQUENCY_EDGE && freq < 1.0){ max = pos2lum((float)i / (float)width); break; } } } /** Returns width of the histogram in pixels */ int Histogram::getWidth(void) { return width; } int Histogram::getHeight(void) { return height; } int Histogram::getBackgroundWidth(void) { return (width + SIDE_BAR*2); } int Histogram::getBackgroundHeight(void) { return (height + BOTTOM_BAR); } float Histogram::getLumMin(void) { return lumMin; } float Histogram::getLumMax(void) { return lumMax; } void Histogram::setSliderPosMin( float pos) { if( pos > sliderPosMax) pos = sliderPosMax; if( pos < 0) pos = 0; sliderPosMin = pos; } float Histogram::getSliderPosMin(void) { return sliderPosMin; } void Histogram::setSliderPosMax( float pos) { if( pos < sliderPosMin) pos = sliderPosMin; if( pos > 1.0) pos = 1.0; sliderPosMax = pos; } float Histogram::getSliderPosMax(void) { return sliderPosMax; } void Histogram::setSliderPosMinMax( float min, float max) { if( min < 0) min = 0; if( max < 0) max = 0; if( min > 1.0) min = 0; if( max > 1.0) max = 1.0; if( min > max) min = max; sliderPosMin = min; sliderPosMax = max; } /** Checks what part of slider was touched. */ void Histogram::processSliderSelection(int xCoord, int yCoord) { redrawStart(); // Hits counter and viewport martix GLint hits, viewp[4]; // Get actual viewport glGetIntegerv(GL_VIEWPORT, viewp); #define BUFFER_SIZE 64 // Table for selection buffer data GLuint selectionBuffer[BUFFER_SIZE]; // Prepare selection buffer glSelectBuffer(BUFFER_SIZE, selectionBuffer); // Change rendering mode glRenderMode(GL_SELECT); // Initializes the Name Stack glInitNames(); // Push 0 (at least one entry) Onto the Stack glPushName(NONE); // Set new projection matrix as a box around xPos, yPos glLoadIdentity(); int hh = glutGet(GLUT_WINDOW_HEIGHT); // Picking matrix at position xCoord, windowSize - yCoord (fliped window Y axis) // and size of 4 units in depth gluPickMatrix(xCoord, hh - yCoord, 4, 4, viewp); glOrtho(0.0f, viewp[2], 0.0f, viewp[3], -10.0f, 10.0f); // last 1.0 -1.0 it's enough drawSlider(); // draw only picked parts hits = glRenderMode(GL_RENDER); if(hits > 0) { for(int i=3; i < BUFFER_SIZE; i+=4) { switch(selectionBuffer[i]) { case 1 :selectionState = LEFT_BAR; break; case 2 :selectionState = RIGHT_BAR; break; case 3 :selectionState = WHOLE_SLIDER; break; default:selectionState = NONE; break; } if(selectionState != NONE) break; } } redrawEnd(); } int Histogram::setSliderSelectionState(SelectedBar newState) { if(newState == LEFT_BAR || newState == RIGHT_BAR || newState == WHOLE_SLIDER || newState == NONE) { selectionState = newState; return (int)selectionState; } else return -1; } SelectedBar Histogram::getSliderSelectionState() { return selectionState; } void Histogram::resetFrequencyMax(void) { frequencyMax = -1; } pfstools-2.2.0/src/pfsglview/m_histogram.cpp0000664000701400070140000006473214105165614017707 0ustar rkm38rkm38/** * @brief PFS library - additional utilities * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2006 Radoslaw Mantiuk * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Radoslaw Mantiuk, * @author Oliver Barth */ #include #include #include #include #include "pfs.h" #include "m_histogram.h" #define BORDER_SIDE 20 #define BORDER_BOTTOM 30 #define BORDER_TOP 5 #define SLIDER_WIDTH 16 #define SCALE_BORDER_TOP 3 #define SCALE_BORDER_BOTTOM 3 #define MIN_LUM 0.00000001f #define MAX_LUM 100000000.0f // WHITE #define W_R 1.0 #define W_G 1.0 #define W_B 1.0 #define W_A 0.45 // RED #define R_R 1.0 #define R_G 0.1 #define R_B 0.1 #define R_A 0.9 // GREEN #define G_R 0.1 #define G_G 0.75 #define G_B 0.1 #define G_A 0.9 // BLUE #define B_R 0.25 #define B_G 0.25 #define B_B 1.0 #define B_A 0.9 /** */ M_Histogram::M_Histogram() : Module() { // set inital height height = 150 ; // set background color of the widget to black winBackgroundColor = new float[4]; for(int i=0; i<3; i++) winBackgroundColor[i] = 0.0f; // no transparency please winBackgroundColor[3] = 1.0f; // array for the frequency analysis frequencyValues = NULL ; // set a fixed size of bins bins = 20000 ; // set sigma values for pre- and postsmoothing // gauss_data = (float)bins/1000 ; // take this for an adaptive approach , experimental gauss_data = 2.5 ; gauss_display = 2.5 ; // enabled smoothing of the intensity scale values by default is_scale_gaussed = 1 ; // disable smoothing of the intensity scale values is_scale_show_max_hue = 0 ; // amplifiy the intensity values by default for better visibility scale_amplifier = 0.5 ; // set the default channel channel = "XYZ" ; // invalidate the calculated maximum frequency frequencyMax = -1; // set the inital slider position sliderPosMin = 0; sliderPosMax = 0; // set the predefined luminance range lumMin = MIN_LUM; lumMax = MAX_LUM; logLumMin = log10(lumMin); logLumMax = log10(lumMax); // set inital hover state hoverState = NONE; virtualWidth = 1.0 ; virtualOffset = 0.5 ; abort_update = 0; } const float hist_color_max = 1.0f; const float hist_color_min = 0.2f; const float hist_color_alpha = 0.9f; M_Histogram::~M_Histogram() { delete [] frequencyValues; } void M_Histogram::redraw(void) { if(frequencyValues == NULL) return ; Module::redrawStart(); drawHistogram(); drawScale(); drawSlider(); drawStatistic(); Module::redrawEnd(); } /** Draws histogram. */ void M_Histogram::drawHistogram() { if(frequencyValues == NULL) return; if( frequencyMax == -1) frequencyMax = getMaxFrequency(); int offsetX = BORDER_SIDE; int offsetY = BORDER_BOTTOM; // if(virtualWidth <= 1) virtualOffset = 0.5 ; float offset = -virtualOffset * (virtualWidth -1.0) * (float)bins ; float barHeight = 0; float center = 0 ; float strech = ((float)width- BORDER_SIDE*2)/(float)bins ; float relative_height ; float c_pos = lum2pos(center) ; glEnable(GL_BLEND); int array_pos ; int ch = 0 ; if(!strcmp(channel, "XYZ") == 0) switch(channel[0]) { case 'X': ch = 1 ; break ; case 'Y': ch = 2 ; break ; case 'Z': ch = 3 ; break ; } float valX = log10(rawX); float valY = log10(rawY); float valZ = log10(rawZ); int indexX = (int)round((valX - logLumMin) / (logLumMax - logLumMin) * (float)bins); int indexY = (int)round((valY - logLumMin) / (logLumMax - logLumMin) * (float)bins); int indexZ = (int)round((valZ - logLumMin) / (logLumMax - logLumMin) * (float)bins); if(rawPosX == -1 || rawPosY == -1){ indexX = -1 ; indexY = -1 ; indexZ = -1 ; } for(int pos = offsetX; pos< width-BORDER_SIDE; pos++) { switch(ch) { case 0: glColor4f(W_R, W_G, W_B, W_A); break ; case 1: glColor4f(R_R, R_G, R_B, R_A); break ; case 2: glColor4f(G_R, G_G, G_B, G_A); break ; case 3: glColor4f(B_R, B_G, B_B, B_A); break ; } int ch_offset = 0 ; if(!is_scale_gaussed) ch_offset = 4 ; // GAUSS DISPLAY HIST float relative_height = 0 ; float his_width = (float)(width-BORDER_SIDE*2) ; float painter = (float)(pos-BORDER_SIDE) / his_width ; float fbins = (float)bins ; float ext_g_weight = 0 ; float ext_g_sigma = gauss_display ; if(!is_scale_gaussed) ext_g_sigma = 0.01 ; float ext_g_width = ceil(ext_g_sigma * 3) ; for(int k = -ext_g_width ; k <= ext_g_width ; k++){ array_pos =(int) (painter * fbins - offset + k * fbins / his_width) /virtualWidth ; if(array_pos < 0 || array_pos >= bins) continue ; float ext_gaussian = 1.0/(ext_g_sigma*sqrt(2.0*M_PI)) * exp(-0.5*pow((float)k/ext_g_sigma,2.0)) ; ext_g_weight += ext_gaussian ; relative_height += ((float)frequencyValues[ch + ch_offset][array_pos]) / frequencyMax * ext_gaussian; } relative_height /= ext_g_weight; if( relative_height > 1){ relative_height = 1; } barHeight = ((float)height - BORDER_BOTTOM -BORDER_TOP) * relative_height; // DRAW HIST if(barHeight > 0) glRectf(pos, offsetY, pos+1, offsetY + barHeight); // DRAW SCALE array_pos = floor((painter * fbins - offset)/virtualWidth) ; int array_pos_min =floor( ((float)(pos-BORDER_SIDE) / his_width * fbins - offset)/virtualWidth) ; int array_pos_max =floor( ((float)(pos+1-BORDER_SIDE) / his_width * fbins - offset)/virtualWidth) ; if(array_pos_min == array_pos_max) array_pos_max++ ; int r = 0 ; int g = 0 ; int b = 0 ; if(ch == 0){ if(indexX >= array_pos_min && indexX < array_pos_max) r = 1.0 ; if(indexY >= array_pos_min && indexY < array_pos_max) g = 1.0 ; if(indexZ >= array_pos_min && indexZ < array_pos_max) b = 1.0 ; }else{ r = 1.0 ; g = 1.0 ; b = 1.0 ; } if( (indexX >= array_pos_min && indexX < array_pos_max && (ch == 0 || ch == 1)) || (indexY >= array_pos_min && indexY < array_pos_max && (ch == 0 || ch == 2)) || (indexZ >= array_pos_min && indexZ < array_pos_max && (ch == 0 || ch == 3)) ){ glColor4f(r, g, b, 0.75); glRectf(pos, offsetY, pos+1, offsetY + (float)height - BORDER_BOTTOM -BORDER_TOP); } if(array_pos > 0 && array_pos < bins) { // for ch == 0 float cr, cg, cb, sum, weight ; // for ch > 0 float value ; float hue = 0 ; if(ch > 0) value = frequencyValues[ch + ch_offset][array_pos] ; // dont waste time, go to the next round if(ch > 0 && value == 0) continue ; if(ch > 0) hue = is_scale_show_max_hue?1.0:pow(value / frequencyMax,scale_amplifier) ; switch(ch) { case 0: cr = frequencyValues[1 + ch_offset][array_pos] ; cg = frequencyValues[2 + ch_offset][array_pos] ; cb = frequencyValues[3 + ch_offset][array_pos] ; sum = (cr + cg +cb) / 3.0 ; // dont waste time, go to the next round if(sum == 0) continue ; weight = cr ; if(cg > weight) weight = cg ; if(cb > weight) weight = cb ; cr /= weight ; cg /= weight ; cb /= weight ; hue = is_scale_show_max_hue?1.0:pow(sum / frequencyMax,scale_amplifier) ; glColor4f(cr, cg, cb, hue); break ; case 1: glColor4f(R_R, R_G, R_B, hue); break ; case 2: glColor4f(G_R, G_G, G_B, hue); break ; case 3: glColor4f(B_R, B_G, B_B, hue); break ; } glRectf(pos, SCALE_BORDER_BOTTOM, pos+1, 0 + BORDER_BOTTOM-1 -SCALE_BORDER_TOP); } } glDisable(GL_BLEND); } /** Draws scale */ void M_Histogram::drawScale() { glColor3f(1.0f, 1.0f, 1.0f); float pos; int offsetX = BORDER_SIDE ; int offsetY = BORDER_BOTTOM; int k1 = 0; // if(virtualWidth <= 1) virtualOffset = 0.5 ; float offset = -virtualOffset * (virtualWidth -1.0) * (width- BORDER_SIDE*2) ; for( float i = (float)logLumMin; i <= (float)logLumMax; i=i+0.25) { pos = lum2pos( pow(10, (float)i)) * ((float)width- BORDER_SIDE*2); pos *= virtualWidth ; pos += offset ; int hh = 2; if( round(i) == i) hh = 4; glRectf( offsetX + pos, offsetY - hh, offsetX + pos + 1, offsetY + hh -1); if( round(i) == i) { if( i < 0 ) { glRasterPos2f( offsetX + pos - 9, offsetY - 15); glutBitmapCharacter(GLUT_BITMAP_8_BY_13, '-'); glutBitmapCharacter(GLUT_BITMAP_8_BY_13, '8' - (k1++)); } else if( i == 0 ) { glRasterPos2f( offsetX + pos - 1, offsetY - 15); glutBitmapCharacter(GLUT_BITMAP_8_BY_13, '0' + i); } else { glRasterPos2f( offsetX + pos - 2, offsetY - 15); glutBitmapCharacter(GLUT_BITMAP_8_BY_13, '0' + i); } } } glRectf( offsetX + (0 * virtualWidth) + offset, offsetY, offsetX + pos + 1, offsetY-1); } /** Draws slider */ void M_Histogram::drawSlider() { // if(virtualWidth <= 1) virtualOffset = 0.5 ; float offset = -virtualOffset * (virtualWidth -1.0) * (width- BORDER_SIDE*2) ; float minValue = (width- BORDER_SIDE*2) * sliderPosMin; float maxValue = (width- BORDER_SIDE*2) * sliderPosMax; int sliderHeight = height -BORDER_BOTTOM -BORDER_TOP ; int offsetX = BORDER_SIDE; int offsetY = height -BORDER_TOP; glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA); glMatrixMode(GL_MODELVIEW) ; // BOTTOM glPushMatrix(); glLoadName(BOTTOM); glColor4f(1.0f, 0.0f, 0.0f, 0.8f); glBegin(GL_QUADS); glVertex3f(offsetX-BORDER_SIDE, 0, -0.5f); glVertex3f(offsetX-BORDER_SIDE, -Y_BAR/2, -0.5f); glVertex3f(offsetX + width - BORDER_SIDE, -Y_BAR/2, -0.5f); glVertex3f(offsetX + width - BORDER_SIDE, 0, -0.5f); glEnd(); glPopMatrix(); offsetX += offset ; minValue *= virtualWidth ; maxValue *= virtualWidth ; // SLIDER if(minValue-SLIDER_WIDTH>0){ glLoadName(WHOLE_SLIDER); glColor4f(1.0f, 0.0f, 0.0f, 0.25f); glBegin(GL_QUADS); glVertex3f(offsetX + 0, offsetY, -0.5f); glVertex3f(offsetX + 0, offsetY - sliderHeight, -0.5f); glVertex3f(offsetX + minValue-SLIDER_WIDTH, offsetY - sliderHeight, -0.5f); glVertex3f(offsetX + minValue-SLIDER_WIDTH, offsetY, -0.5f); glEnd(); } if(maxValue+SLIDER_WIDTH < (width-2*BORDER_SIDE) * virtualWidth ){ glLoadName(WHOLE_SLIDER); glColor4f(1.0f, 0.0f, 0.0f, 0.25f); glBegin(GL_QUADS); glVertex3f(offsetX + maxValue+SLIDER_WIDTH, offsetY, -0.5f); glVertex3f(offsetX + maxValue+SLIDER_WIDTH, offsetY - sliderHeight, -0.5f); glVertex3f(offsetX + (width-2*BORDER_SIDE) * virtualWidth , offsetY - sliderHeight, -0.5f); glVertex3f(offsetX + (width-2*BORDER_SIDE) * virtualWidth , offsetY, -0.5f); glEnd(); } glLoadName(WHOLE_SLIDER); glColor4f(0.0f, 0.0f, 0.0f, 0.0f); glBegin(GL_QUADS); glVertex3f(offsetX + minValue, offsetY, -0.5f); glVertex3f(offsetX + minValue, offsetY - sliderHeight, -0.5f); glVertex3f(offsetX + maxValue, offsetY - sliderHeight, -0.5f); glVertex3f(offsetX + maxValue, offsetY, -0.5f); glEnd(); // LEFT glPushMatrix(); glLoadName(LEFT_BAR); glColor4f(1.0f, 1.0f, 1.0f, 0.3f); glBegin(GL_QUADS); glVertex3f(offsetX + minValue-SLIDER_WIDTH, offsetY, -0.5f); glVertex3f(offsetX + minValue-SLIDER_WIDTH, offsetY - sliderHeight, -0.5f); glVertex3f(offsetX + minValue, offsetY - sliderHeight, -0.5f); glVertex3f(offsetX + minValue, offsetY, -0.5f); glEnd(); // RIGHT glPushMatrix(); glLoadName(RIGHT_BAR); glColor4f(1.0f, 1.0f, 1.0f, 0.3f); glBegin(GL_QUADS); glVertex3f(offsetX + maxValue, offsetY, -0.5f); glVertex3f(offsetX + maxValue, offsetY - sliderHeight, -0.5f); glVertex3f(offsetX + maxValue+SLIDER_WIDTH, offsetY - sliderHeight, -0.5f); glVertex3f(offsetX + maxValue+SLIDER_WIDTH, offsetY, -0.5f); glEnd(); glPopMatrix(); // BACK + SCALE offsetX = BORDER_SIDE; offsetY = height ; glPushMatrix(); glLoadName(BACK); glColor4f(0.0f, 1.0f, 0.0f, 0.0f); glBegin(GL_QUADS); glVertex3f(0, 0, -0.5f); glVertex3f(0, offsetY, -0.5f); glVertex3f(offsetX + width - BORDER_SIDE, offsetY, -0.5f); glVertex3f(offsetX + width - BORDER_SIDE, 0, -0.5f); glEnd(); glPopMatrix(); glDisable(GL_BLEND); } /** Draws statistic of a current image. */ void M_Histogram::drawStatistic() { int offsetX = width - 24*8 -BORDER_SIDE; int offsetY = height - 13 -BORDER_TOP ; glColor3f(0.5f, 1.0f, 0.5f); glRasterPos2f( offsetX, offsetY ); char ss[200]; sprintf( ss, "logLum.:<%.3f, %.3f>" , log10(pos2lum(sliderPosMin)), log10(pos2lum(sliderPosMax))); int len = (int) strlen(ss); for (int i = 0; i < len; i++) { glutBitmapCharacter(GLUT_BITMAP_8_BY_13, ss[i]); } } void M_Histogram::setChannel(const char* ch) { channel = ch; } void M_Histogram::clearChannel(int ch, void CB(void)) { if(frequencyValues == NULL) return ; #pragma omp parallel for for( int i = 0; i < bins; i++ ){ if(abort_update == 1) continue ; frequencyValues[ch][i] = 0; frequencyValues[ch+4][i] = 0; } #pragma omp parallel for for( int i = 0; i < bins; i++ ){ if(abort_update == 1) continue ; frequencyValues[8][i] = 0; } CB() ; } /** Creates histogram. Histogram will have 'width' bins. */ void M_Histogram::computeFrequency(int ch, const pfs::Array2D *image,void CB(void)) { dbs("Histogram::computeFrequency") ; if(ch == 0 && image == NULL){ #pragma omp parallel for for( int i = 0; i < bins; i++ ){ if(abort_update == 1) continue ; frequencyValues[0][i] = frequencyValues[1][i] +frequencyValues[2][i] +frequencyValues[3][i] ; frequencyValues[0][i] = ceil((float)frequencyValues[0][i] / 3.0) ; frequencyValues[4][i] = frequencyValues[5][i] +frequencyValues[6][i] +frequencyValues[7][i] ; frequencyValues[4][i] = (float)frequencyValues[4][i] / 3.0 ; if(i % 10 == 0) CB() ; } return ; } const int size = image->getRows()*image->getCols(); if(frequencyValues == NULL){ // binborder = new float[bins+1]; frequencyValues = new int*[9]; frequencyValues[0] = new int[bins]; frequencyValues[1] = new int[bins]; frequencyValues[2] = new int[bins]; frequencyValues[3] = new int[bins]; frequencyValues[4] = new int[bins]; frequencyValues[5] = new int[bins]; frequencyValues[6] = new int[bins]; frequencyValues[7] = new int[bins]; frequencyValues[8] = new int[bins]; // for( int i = 0; i < bins; i++ ){ // frequencyValues[0][i] = 0; // frequencyValues[1][i] = 0; // frequencyValues[2][i] = 0; // frequencyValues[3][i] = 0; // // frequencyValues[4][i] = 0; // frequencyValues[5][i] = 0; // frequencyValues[6][i] = 0; // frequencyValues[7][i] = 0; // } // for(int k = 0 ; k <= bins ; k++){ // binborder[k] = pow10( (double)((logLumMax - logLumMin) / (double)bins * (double)k + logLumMin)); // } } // int lastbin = -1 ; #pragma omp parallel for for( int i = 0; i < size; i=i+1 ) { if(abort_update == 1) continue ; float val = (*image)(i); if( val <= 0 || val > lumMax) { //fprintf( stderr, "WARNING: pixel luminance out of range (Histogram::computeFrequency())\n"); continue; } val = log10(val); // Calculate frequency value index in table: <0, with> int index = (int)round((val - logLumMin) / (logLumMax - logLumMin) * (float)bins); // int index = -1 ; // // if(lastbin != -1){ //// printf("lb: %d\n", lastbin) ; // int lastbin_b = lastbin -4 ; // int lastbin_e = lastbin +4 ; // if(lastbin_b < 0) lastbin_b = 0 ; // if(lastbin_e > bins) lastbin_e = bins ; // // index = findBin(val, lastbin_b, lastbin_e) ; // } // if(index == -1) // index = findBin(val, 0, bins) ; // // if(index != -1) lastbin = index ; if( index > bins || index < 0 ) continue; if( index == bins ) index = bins - 1; // Increase the counter of this value frequencyValues[ch+4][index] += 1; frequencyValues[ch][index] += 1; if(i % 10 == 0) { CB() ; } } float his_width = (float)(width-BORDER_SIDE*2) ; float fbins = (float)bins ; for(int k = 0 ; k < bins ; k++){ if(abort_update == 1) continue ; float rel = 0 ; float g_weight = 0 ; float g_sigma = gauss_data ; float g_width = ceil(g_sigma * 3) ; for(int j = -g_width ; j <= g_width ; j++){ if(k +j < 0 || k +j >= bins) continue ; float gaussian = 1.0/(g_sigma*sqrt(2.0*M_PI)) * exp(-0.5*pow((float)j/g_sigma,2.0)) ; g_weight += gaussian ; rel += (float)frequencyValues[ch+4][k+j] * gaussian; } // printf("weight: %f\n", g_weight) ; rel /= g_weight ; frequencyValues[ch][k] = rel ; if(k % 10 == 0){ CB() ; } } dbe("Histogram::computeFrequency") ; } int M_Histogram::findBin(float val, int start, int stop){ // fprintf(stderr, "d1 %d %d\n", start, stop) ; // exit(0) ; if(val < binborder[start] && val > binborder[stop]) { // fprintf(stderr, "d1") ; return -1 ; } if(start == stop || start == stop-1){ if(val >= binborder[start] && val < binborder[start+1] ) { // fprintf(stderr, "d2") ; return start; }else{ // fprintf(stderr, "d3") ; return -1 ; } } int left = findBin(val, start, stop - (stop - start)/2) ; int right = findBin(val, start + (stop - start)/2, stop) ; // fprintf(stderr, "d4") ; if(left >= 0 ) return left ; // fprintf(stderr, "d5") ; if(right >= 0 ) return right ; // fprintf(stderr, "d6") ; return -1 ; } /** Computes the maximum frequency in an image based on histogram data. */ float M_Histogram::getMaxFrequency() { if(frequencyValues == NULL) return -1; int maxFreq = -1; for( int i = 0; i < bins; i++ ) { if( frequencyValues[0][i] > maxFreq ) maxFreq = frequencyValues[0][i]; if( frequencyValues[1][i] > maxFreq ) maxFreq = frequencyValues[1][i]; if( frequencyValues[2][i] > maxFreq ) maxFreq = frequencyValues[2][i]; if( frequencyValues[3][i] > maxFreq ) maxFreq = frequencyValues[3][i]; } if(maxFreq == 0){ maxFreq = -1; for( int i = 0; i < bins; i++ ) { if( frequencyValues[4][i] > maxFreq ) maxFreq = frequencyValues[4][i]; if( frequencyValues[5][i] > maxFreq ) maxFreq = frequencyValues[5][i]; if( frequencyValues[6][i] > maxFreq ) maxFreq = frequencyValues[6][i]; if( frequencyValues[7][i] > maxFreq ) maxFreq = frequencyValues[7][i]; } } return maxFreq; } /** Returns frequence of the bin = max_bin - PERCENTIL * number_of_bins. Very light and very dark bins * are not taken into consideration in this computations. It allows to skip very dark (black) and/or * very light (white) pixels which can cover large areas of an image and lower the height of histogram for remaining * pixels (undesirable situation: flat histogram with one large pick for very light bin). */ float M_Histogram::getHighFrequency() { if(frequencyValues == NULL) return -1; std::vector vec; for( int i = 0; i < bins; i++ ) { vec.push_back((float)frequencyValues[0][i]); vec.push_back((float)frequencyValues[1][i]); vec.push_back((float)frequencyValues[2][i]); vec.push_back((float)frequencyValues[3][i]); } std::sort(vec.begin(), vec.end()); #define PERCENTIL 0.99 float highFreq = vec[(int)((float)vec.size() * PERCENTIL)]; // returns high frequency only if max frequency is 3 or more times higher float maxFreq = getMaxFrequency(); if( (3*highFreq) > maxFreq) return maxFreq; else return highFreq; } /** Converts horizontal position in the histogram into luminance value. The position must be in range <0,1>. */ float M_Histogram::pos2lum( float pos) { if( pos < 0) pos = 0; if( pos > 1) pos = 1; return pow( 10, pos * (logLumMax - logLumMin) + logLumMin); } /** Converts luminance value into position in range <0,1>. */ float M_Histogram::lum2pos( float lum) { if( lum < lumMin) lum = lumMin; if( lum > lumMax) lum = lumMax; return (log10(lum) - logLumMin) / (logLumMax - logLumMin); } /** Computes initial position of the slider. The calculation are based on the shape of histogram. */ #define FREQUENCY_EDGE 0.05 void M_Histogram::computeLumRange( float& min, float& max) { if(frequencyValues == NULL) return; float maxFreq = getMaxFrequency(); float freq; for( int i = 0; i < bins; i++) { freq = frequencyValues[0][i] / maxFreq; if( freq > FREQUENCY_EDGE && freq < 1.0){ min = pos2lum((float)i / (float)bins); break; } } for( int i = (bins-1); i >=0; i--) { freq = frequencyValues[0][i] / maxFreq; if( freq > FREQUENCY_EDGE && freq < 1.0){ max = pos2lum((float)i / (float)bins); break; } } } float M_Histogram::getPeak() { if(frequencyValues == NULL) return -1; float maxFreq = getMaxFrequency(); float freq = 0 ; float j = -1 ; for( int i = 0; i < bins; i++) { if(freq < frequencyValues[0][i]){ freq = frequencyValues[0][i] ; j = i ; } } return pos2lum((float)j / (float)bins); } int M_Histogram::getSidebarWidth(void) { return BORDER_SIDE ; } /** Returns width of the histogram in pixels */ int M_Histogram::getInnerWidth(void) { return width - BORDER_SIDE * 2; } int M_Histogram::getInnerHeight(void) { return height - BORDER_BOTTOM - BORDER_TOP; } int M_Histogram::getBackgroundWidth(void) { return (width); } int M_Histogram::getBackgroundHeight(void) { return (height); } float M_Histogram::getLumMin(void) { return lumMin; } float M_Histogram::getLumMax(void) { return lumMax; } void M_Histogram::setSliderPosMin( float pos) { if( pos > sliderPosMax) pos = sliderPosMax; if( pos < 0) pos = 0; sliderPosMin = pos; } float M_Histogram::getSliderPosMin(void) { return sliderPosMin; } void M_Histogram::setSliderPosMax( float pos) { if( pos < sliderPosMin) pos = sliderPosMin; if( pos > 1.0) pos = 1.0; sliderPosMax = pos; } float M_Histogram::getSliderPosMax(void) { return sliderPosMax; } void M_Histogram::setSliderPosMinMax( float min, float max) { if( min < 0) min = 0; if( max < 0) max = 0; if( min > 1.0) min = 0; if( max > 1.0) max = 1.0; if( min > max) min = max; sliderPosMin = min; sliderPosMax = max; } /** Checks what part of slider was touched. */ void M_Histogram::processPointerPosition(int xCoord, int yCoord, int pan) { if(frequencyValues == NULL) return; redrawStart(); // Hits counter and viewport martix GLint hits, viewp[4]; // Get actual viewport glGetIntegerv(GL_VIEWPORT, viewp); #define BUFFER_SIZE 64 // Table for selection buffer data GLuint selectionBuffer[BUFFER_SIZE]; // Prepare selection buffer glSelectBuffer(BUFFER_SIZE, selectionBuffer); // Change rendering mode glRenderMode(GL_SELECT); // Initializes the Name Stack glInitNames(); // Push 0 (at least one entry) Onto the Stack glPushName(NONE); // Set new projection matrix as a box around xPos, yPos glLoadIdentity(); int hh = glutGet(GLUT_WINDOW_HEIGHT); // Picking matrix at position xCoord, windowSize - yCoord (fliped window Y axis) // and size of 4 units in depth gluPickMatrix(xCoord, hh - yCoord, 1, 1, viewp); //gluPickMatrix(xCoord, hh - yCoord, 1, 1, viewp); glOrtho(0.0f, viewp[2], 0.0f, viewp[3], -10.0f, 10.0f); // last 1.0 -1.0 it's enough drawSlider(); // draw only picked parts hits = glRenderMode(GL_RENDER); // printf("\n\nHITS: %d %d\n", hits, BUFFER_SIZE) ; if(hits > 0) { for(int i=3; i < hits*4; i+=4) { // printf("HIT: %d - %u \n", i-3, selectionBuffer[i-3] ) ; // printf("HIT: %d - %u \n", i-2, selectionBuffer[i-2] ) ; // printf("HIT: %d - %u \n", i-1, selectionBuffer[i-1] ) ; // printf("HIT: %d - %u \n", i, selectionBuffer[i-0] ) ; switch(selectionBuffer[i]) { case 1 :hoverState = LEFT_BAR; glutSetCursor(GLUT_CURSOR_LEFT_SIDE); break; case 2 :hoverState = RIGHT_BAR; glutSetCursor(GLUT_CURSOR_RIGHT_SIDE); break; case 3 :hoverState = WHOLE_SLIDER; !pan?glutSetCursor(GLUT_CURSOR_LEFT_RIGHT):(virtualWidth>1?glutSetCursor(GLUT_CURSOR_INFO):glutSetCursor(GLUT_CURSOR_INHERIT));break; case 4 :hoverState = BOTTOM; glutSetCursor(GLUT_CURSOR_BOTTOM_SIDE);break; case 5 :hoverState = BACK; !pan?glutSetCursor(GLUT_CURSOR_LEFT_RIGHT):(virtualWidth>1?glutSetCursor(GLUT_CURSOR_INFO):glutSetCursor(GLUT_CURSOR_BOTTOM_SIDE));break; default:hoverState = NONE; glutSetCursor(GLUT_CURSOR_INHERIT); break; } if(hoverState != NONE) break; } } else { hoverState = NONE; //glutSetCursor(GLUT_CURSOR_INHERIT); glutSetCursor(GLUT_CURSOR_CROSSHAIR); } redrawEnd(); } int M_Histogram::setSubComponentHoverState(SubComponent newState) { if(newState == LEFT_BAR || newState == RIGHT_BAR || newState == WHOLE_SLIDER || newState == NONE) { hoverState = newState; return (int)hoverState; } else return -1; } void M_Histogram::setVisible(bool _isVisible) { isVisible = _isVisible; if(!getVisible()) setSubComponentHoverState(NONE); } M_Histogram::SubComponent M_Histogram::getSubComponentHoverState() { return hoverState; } void M_Histogram::resetFrequencyMax(void) { frequencyMax = -1; } void M_Histogram::setVirtualWidth(float width) { virtualWidth = width ; // printf("VW: %f\n",virtualWidth) ; } float M_Histogram::getVirtualWidth() { return virtualWidth ; } void M_Histogram::setVirtualOffset(float offset) { virtualOffset = offset ; // printf("VW: %f\n",virtualOffset) ; } float M_Histogram::getVirtualOffset() { return virtualOffset ; } int M_Histogram::getChannelAsInt(){ if(strcmp(channel, "XYZ") == 0){ return 0 ; } else { switch(channel[0]) { case 'X': return 1 ; case 'Y': return 2 ; default: case 'Z': return 3 ; } } } int M_Histogram::get_is_scale_gaussed() const { return is_scale_gaussed; } void M_Histogram::set_is_scale_gaussed(int is_scale_gaussed) { this->is_scale_gaussed = is_scale_gaussed; } int M_Histogram::get_is_scale_show_max_hue() const { return is_scale_show_max_hue; } void M_Histogram::set_is_scale_show_max_hue(int is_scale_show_max_hue) { this->is_scale_show_max_hue = is_scale_show_max_hue; } void M_Histogram::setRawData( int x, int y, float X, float Y, float Z) { rawPosX = x; rawPosY = y; rawX = X; rawY = Y; rawZ = Z; } pfstools-2.2.0/src/pfsglview/m_histogram.h0000664000701400070140000001150414105165614017341 0ustar rkm38rkm38/** * @brief PFS library - additional utilities * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2006 Radoslaw Mantiuk * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Radoslaw Mantiuk, * @author Oliver Barth */ #ifndef M_HISTOGRAM_H #define M_HISTOGRAM_H #include "module.h" class M_Histogram : public Module { private: GLfloat* backgroundColor; int bins; const char* channel ; float* binborder ; float scale_amplifier ; int rawPosX, rawPosY; float rawX, rawY, rawZ; public: int abort_update ; private: void drawScale(); void drawStatistic(); void drawSlider(); int findBin(float val, int start, int stop) ; public: M_Histogram(); ~M_Histogram(); // ************************************ // inherited methods // ************************************ void setVisible(bool _isVisible) ; // ************************************ // ************************************ // frequency related functions // ************************************ private: float frequencyMax; int** frequencyValues; public: float getHighFrequency() ; float getMaxFrequency() ; void resetFrequencyMax(void); void computeFrequency(int channel, const pfs::Array2D *image, void CB(void)); // ************************************ // ************************************ // dimension realted functions // ************************************ public: int getInnerWidth( void); int getInnerHeight( void); int getBackgroundWidth(void); int getBackgroundHeight(void); // ************************************ // ************************************ // luminance related functions // ************************************ private: float lumMin, lumMax; float logLumMin, logLumMax; public: float getLumMin(); float getLumMax(); float pos2lum( float pos); float lum2pos( float lum); void computeLumRange( float& min, float& max); // ************************************ // ************************************ // subcomponent related functions // ************************************ public: enum SubComponent{ NONE, LEFT_BAR, RIGHT_BAR, WHOLE_SLIDER, BOTTOM, BACK }; private: float sliderPosMin, sliderPosMax; // luminance values for start and end of slider SubComponent hoverState; public: void processPointerPosition(int xCoord, int yCoord,int pan); int setSubComponentHoverState(SubComponent newState); SubComponent getSubComponentHoverState(); void setSliderPosMin( float pos); float getSliderPosMin(void); void setSliderPosMax( float pos); float getSliderPosMax(void); void setSliderPosMinMax( float min, float max); // ************************************ // ************************************ // virtual size related functions // ************************************ private: float virtualWidth ; float virtualOffset ; public: void setVirtualWidth(float width) ; float getVirtualWidth(); void setVirtualOffset(float offset) ; float getVirtualOffset(); // ************************************ // ************************************ // visualization related functions // ************************************ private: float gauss_data; float gauss_display; int is_scale_gaussed ; int is_scale_show_max_hue ; public: // gaussian smoothing of the histogram function values int get_is_scale_gaussed() const ; void set_is_scale_gaussed(int is_scale_gaussed) ; // gaussian smoothing of the intensity scale values int get_is_scale_show_max_hue() const ; void set_is_scale_show_max_hue(int is_scale_show_max_hue) ; // ************************************ void setChannel(const char* ch) ; void clearChannel(int channel, void CB(void)); void drawHistogram(); void redraw(void); int getChannelAsInt() ; float getPeak() ; void setRawData(int x, int y, float X, float Y, float Z); int getSidebarWidth(void) ; }; #endif pfstools-2.2.0/src/pfsglview/winstat.cpp0000664000701400070140000000576414105165614017067 0ustar rkm38rkm38/** * @brief PFS library - additional utilities * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2006 Radoslaw Mantiuk * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Radoslaw Mantiuk, * */ #include #include #include "glenv.h" #include "winstat.h" WinStat::WinStat() : RMGLWin() { colR = -1; colG = -1; colB = -1; frameNo = -1; channel = ""; rawPosX = -1; bZoom = true; } WinStat::~WinStat() { } void WinStat::redraw(void) { if( RMGLWin::redrawStart()) return; char ss[200]; glColor3f( 0.0f, 0.0f, 0.0f); glRasterPos2f( 5.0f, 29.0f); if( frameNo != -1) { sprintf( ss, "Frame: %d", frameNo); for (int i = 0; i < (int) strlen(ss); i++) glutBitmapCharacter(GLUT_BITMAP_HELVETICA_10, ss[i]); } //sprintf( ss, "Max freq: %.0f %s", lumMax, mappingMode); char szoom[10]; if( bZoom == true) strcpy( szoom, "ZOOM"); else strcpy( szoom, "PAN"); sprintf( ss, " %s %s %s", mappingMode, channel, szoom); for (int i = 0; i < (int) strlen(ss); i++) glutBitmapCharacter(GLUT_BITMAP_HELVETICA_10, ss[i]); glRasterPos2f( 5.0f, 17.0f); if( colR >= 0) { sprintf( ss, "screen: %d,%d : %d,%d,%d", pixelX, pixelY, colR, colG, colB); for (int i = 0; i < (int) strlen(ss); i++) glutBitmapCharacter(GLUT_BITMAP_HELVETICA_10, ss[i]); } glRasterPos2f( 5.0f, 5.0f); if( rawPosX >=0) { sprintf( ss, "data: %d,%d : %.6f,%.6f,%.6f", rawPosX, rawPosY, rawX, rawY, rawZ); for (int i = 0; i < (int) strlen(ss); i++) glutBitmapCharacter(GLUT_BITMAP_HELVETICA_10, ss[i]); } RMGLWin::redrawEnd(); } void WinStat::setPixelData( int x, int y, int r, int g, int b) { pixelX = x; pixelY = y; colR = r; colB = b; colG = g; } void WinStat::setMaxFreq(float val) { lumMax = val; } void WinStat::setMapping(const char* sval) { mappingMode = sval; } void WinStat::setFrameNo(int no) { frameNo = no; } void WinStat::setChannel(const char* ch) { channel = ch; } void WinStat::setRawData( int x, int y, float X, float Y, float Z) { rawPosX = x; rawPosY = y; rawX = X; rawY = Y; rawZ = Z; } void WinStat::setBZoom(bool bb) { bZoom = bb; } pfstools-2.2.0/src/pfsglview/m_status.cpp0000664000701400070140000000704514105165614017227 0ustar rkm38rkm38/** * @brief PFS library - additional utilities * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2006 Radoslaw Mantiuk * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Radoslaw Mantiuk, * @author Oliver Barth */ #include #include #include "m_status.h" /* * */ SC_Status::SC_Status() : Module() { valueR = -1; valueG = -1; valueB = -1; frameNo = -1; channel = ""; rawPositionX = -1; zoom_scale = 1 ; winBackgroundColor = new float[4]; for(int i=0; i<3; i++) winBackgroundColor[i] = 0.0f; winBackgroundColor[3] = 0.75f; } SC_Status::~SC_Status() { } void SC_Status::redraw(void) { if( Module::redrawStart()) return; char buffer[256]; glColor3f(0.5f, 1.0f, 0.5f); glRasterPos2f( 5.0f, 41.0f); if( frameNo != -1) { sprintf( buffer, "Frame: %d", frameNo); for (int i = 0; i < (int) strlen(buffer); i++) glutBitmapCharacter(GLUT_BITMAP_8_BY_13, buffer[i]); } sprintf( buffer, " %s %s %s", mappingMode, channel, navigationMode); for (int i = 0; i < (int) strlen(buffer); i++) glutBitmapCharacter(GLUT_BITMAP_8_BY_13, buffer[i]); glRasterPos2f( 5.0f, 29.0f); sprintf( buffer, "zoom scale: %.6f", zoom_scale); for (int i = 0; i < (int) strlen(buffer); i++) glutBitmapCharacter(GLUT_BITMAP_8_BY_13, buffer[i]); glRasterPos2f( 5.0f, 17.0f); if( valueR >= 0) { sprintf( buffer, "screen : %4d, %4d : %8d, %8d, %8d", positionX, positionY, valueR, valueG, valueB); for (int i = 0; i < (int) strlen(buffer); i++) glutBitmapCharacter(GLUT_BITMAP_8_BY_13, buffer[i]); } glRasterPos2f( 5.0f, 5.0f); if( rawPositionX >=0) { sprintf( buffer, "data : %4d, %4d : %5.2e, %5.2e, %5.2e", rawPositionX, rawPositionY, rawValueX, rawValueY, rawValueZ); for (int i = 0; i < (int) strlen(buffer); i++) glutBitmapCharacter(GLUT_BITMAP_8_BY_13, buffer[i]); } Module::redrawEnd(); } void SC_Status::setPixelData( int x, int y, int r, int g, int b) { positionX = x; positionY = y; valueR = r; valueG = g; valueB = b; } void SC_Status::setRawData( int x, int y, float X, float Y, float Z) { rawPositionX = x; rawPositionY = y; rawValueX = X; rawValueY = Y; rawValueZ = Z; } void SC_Status::setMaxFreq(float _max_frequency) { lumMax = _max_frequency; } void SC_Status::setMapping(const char* _mappingMode) { mappingMode = _mappingMode; } void SC_Status::setNavMode(const char* _navigationMode) { navigationMode = _navigationMode; } void SC_Status::setFrameNo(int _frameNo) { frameNo = _frameNo; } void SC_Status::setChannel(const char* _channel) { channel = _channel; } void SC_Status::setZoomScale(float zoom_scale) { this->zoom_scale = zoom_scale; } pfstools-2.2.0/src/pfsglview/picture_io.h0000664000701400070140000001006114105165614017167 0ustar rkm38rkm38/** * @brief PFS library - additional utilities * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2006 Radoslaw Mantiuk * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Radoslaw Mantiuk, * @author Oliver Barth */ #ifndef PICTURE_IO_H #define PICTURE_IO_H #include #include "pfs.h" #define MAX_FRAMES_IN_MEMORY 10 enum LumMappingMethod { MAP_LINEAR, MAP_GAMMA1_4, MAP_GAMMA1_8, MAP_GAMMA2_2, MAP_GAMMA2_6, MAP_LOGARITHMIC }; static const char* lumMappingName[] = { // must be consistent with LumMappingMethod "Linear", "Gamma 1.4", "Gamma 1.8", "Gamma 2.2", "Gamma 2.6", "Logarithmic" }; class PictureIO { private: pfs::Frame *pfsFrame; int width; int height; const char *currentFileName; const char *visibleChannel; pfs::Array2D *chR, *chG, *chB; std::list frameList; std::list::iterator currentFrame; int frameNo; unsigned char* data; LumMappingMethod imageMappingMethod; float minLuminance, maxLuminance; int is_show_clipping ; public: const char* CHANNEL_XYZ; const char* CHANNEL_X; const char* CHANNEL_Y; const char* CHANNEL_Z; int abort_update ; PictureIO(LumMappingMethod mappingMethod, float minLuminance, float maxLumuminance); ~PictureIO(); void gotoNextFrame(); void gotoPreviousFrame(); void changeMapping( LumMappingMethod mappingMethod, float minLum, float maxLum); void changeMapping( float minLum, float maxLum); void changeMapping( LumMappingMethod mappingMethod); void changeMapping( void); void setMinLum( float val); void setMaxLum( float val); unsigned char* getImageData( void); void setMappingMethod( LumMappingMethod val); LumMappingMethod getMappingMethod( void); const pfs::Array2D* getPrimaryChannel(); int loadPicture(const char* location, int width, int height); const char *getCurrentFileName(); const char *getVisibleChannel(); void setVisibleChannel(const char *channel); pfs::Frame *getFrame(); void setFrame(pfs::Frame *pfsFrame, const char *channel); int save(void); int getWidth(); int getHeight(); int getPixel(int ch, int c, int r); int getPixelR(int x, int y); int getPixelG(int x, int y); int getPixelB(int x, int y); float getLumMin(void); float getLumMax(void); float computeLumMin(void); float computeLumMax(void); int getFrameNo(void); float getDynamicRange(void); std::vector getChannelNames(); int getRawData( int x, int y, float& XX, float& YY, float& ZZ); void updateMapping( void CB(void)); int get_is_show_clipping() const ; void set_is_show_clipping(int is_show_clipping) ; inline int fastMap( LumMappingMethod mappingMethod, float v, float minValue, float maxValue, float ranger) ; inline int fastCheck( LumMappingMethod mappingMethod, float v, float minValue, float maxValue, float ranger) ; private: bool hasColorChannels( pfs::Frame *frame ); inline int binarySearchPixels(const float lum, const float *lumMap, const int lumSize); float getInverseMapping( LumMappingMethod mappingMethod, float v, float minValue, float maxValue ); bool readNextFrame(); }; class PFSglViewException { }; #endif pfstools-2.2.0/src/pfsglview/pfsglview.cpp0000664000701400070140000014475714105165614017412 0ustar rkm38rkm38/** * @brief PFS library - additional utilities * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2006 - 2012 Radoslaw Mantiuk, Oliver Barth * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Radoslaw Mantiuk, * @author Oliver Barth */ //TODO slider geht nicht wirklich auf 0 //TODO menu geht nicht mehr ... repost porblem //TODO alte fenster position merken #include #include #include #include #include #include #include #include #include #include "glenv.h" #include "picture_io.h" #include "m_histogram.h" #include "m_status.h" #include "m_on_screen_display.h" #define PROG_NAME "pfsGLview" GLint win, mainMenu, mappingSubmenu, channelSubmenu, mouse=0; // for holding menu and window ids PictureIO* bitmap; PictureIO* bitmap2; M_Histogram* m_histogram; SC_Status* m_status; M_OnScreenDisplay* m_osd_help; M_OnScreenDisplay* m_osd_loading; M_OnScreenDisplay* m_osd_mapping; GLboolean firstTime = true; bool verbose = false; enum MenuIds { MENU_SEPARATOR, MENU_NONE, MENU_HISTOGRAM, MENU_MONITOR_LUM, MENU_MAX_LUM, MENU_EXIT, MENU_MAPPING_LINEAR, MENU_MAPPING_LOG, MENU_MAPPING_GAMMA_1_4, MENU_MAPPING_GAMMA_1_8, MENU_MAPPING_GAMMA_2_2, MENU_MAPPING_GAMMA_2_6, MENU_SAVE, MENU_CHANNEL_XYZ, MENU_CHANNEL_X, MENU_CHANNEL_Y, MENU_CHANNEL_Z, MENU_CHANNEL, MENU_INFO, MENU_HELP, MENU_FRAME_NEXT, MENU_FRAME_PREVIOUS, MENU_HIST_RIGHT, MENU_HIST_LEFT, MENU_HIST_INCREASE, MENU_HIST_DECREASE, MENU_ZOOM_IN, MENU_ZOOM_OUT, MENU_ZOOM_RESET, MENU_SELECT_POINTS, MENU_FIX_CURSOR }; std::string loadtext[] = {"generating histograms"} ; std::string maptext[] = {"mapping values"} ; //std::string helptext2[] = { // " ", //} ; std::string helptext[] = { "keyboard", "--------------------------------------------------", "F1 - help", "arrow keys - pan view", "c - cycle channel", ". - zoom in", ", - zoom out", "r - reset", "-/= - move dynamic range", "] - increase exposure", "[ - decrease exposure", "i - toggle info panel", "h - toggle histogram panel", "CTRL-L - LDR", "\\ - fit dynamic range", "1 - mapping gamma 1.4", "2 - mapping gamma 1.8", "3 - mapping gamma 2.2", "4 - mapping gamma 2.6", "l - mapping linear", "o - mapping log", "s - set marking points", "n - next frame", "p - previous frame", "f - fix data position", "g - toggle spectrogram Gaussian", "m - toggle spectrogram maximum hue", "x - toggle over and under exposure", "space - toggle left and middle mouse button", "q/ESC - quit program", "", "mouse", "--------------------------------------------------", "left button - pan", "middle button - zoom", "wheel - zoom", "", "exposure", "--------------------------------------------------", "red - all channels under exposed", "green - at least one channel over OR under exposed", "magenta - at least one channel over AND one under exposed", "blue - all channels over exposed" } ; #define INIT_MIN_LUM 0.1f #define INIT_MAX_LUM 1.0f // the area where the M_Status and M_OnScreenDisplay is located #define INFOAREA_WIDTH 500 #define INFOAREA_HEIGHT 55 #define INFOAREA_POS_X 10 #define INFOAREA_POS_Y 10 #define OnScreenDisplay_PERCENTAGE_MAX .85 #define OnScreenDisplay_PERCENTAGE_MIN .55 #define ZOOM_SCALE_MIN 0.1f #define ZOOM_SCALE_MAX 100.0f #define ZOOM_SCALE_STEP 0.005f #define ZOOM_MOVE_STEP 2.0f #define MONITOR_LUM_MIN 0.01f #define MONITOR_LUM_MAX 1.0f #define SLIDER_STEP 5 #define PAN_STEP 10 const char* channel ; /** Mouse listener */ int cursorPosX = -1; int cursorPosY = -1; int cursorPosX_fixed = -1; int cursorPosY_fixed = -1; int cursorPosX_fix = -1 ; int cursorPosY_fix = -1 ; int pfs_wait = 0; void resetHistogram(void); void loadPicture(); void sliderMoveMax( int shift, int hwidth); void sliderMoveMin( int shift, int hwidth); void setWindowTitle( void); //============================================ struct ZOOM { float scale; float scale_default; float x; float y; int vX, vY, vSizeX, vSizeY; int prevX, prevY; bool pan; } szoom; struct point{ float x; float y; }; point points[1000] ; int point_counter = 0; float zoom_akku_x = 0 ; float zoom_akku_y = 0 ; int winWidth ; int winHeight ; int wait_to_start = 0 ; int select_points = 0 ; int fix_cursor = 0 ; int resetFMax = 0 ; int ident = 1 ; struct VIEWPORT { GLint xPos,yPos; GLsizei xSize, ySize; } viewport; float ***image_original; /** */ void zoomReset(void) { dbs("zoomReset") ; szoom.scale = szoom.scale_default; szoom.x = 0; szoom.y = 0; szoom.prevX = -1; szoom.prevY = -1; szoom.pan = true; // TODO getting the width and height here will freak out the program // and exit with a sigsev int height = winHeight -Y_BAR - m_histogram->getHeight() -Y_BAR/2; int width = winWidth -X_BAR ; // float ratio = (float)bitmap->getHeight() / (float)bitmap->getWidth(); float ratio2 = (float)height/(float)width ; szoom.scale = 1.0f; // default scale coefficient if( ratio < ratio2) { // vertical if( bitmap->getWidth() > width) szoom.scale = (float)(width) / (float)bitmap->getWidth(); } else { // horizontal if( bitmap->getHeight() > height) szoom.scale = (float)(height) / (float)bitmap->getHeight(); } szoom.scale_default = szoom.scale; m_histogram->setVirtualWidth(1.0) ; m_histogram->setVirtualOffset(0.5) ; dbe("zoomReset") ; } void zoomIncrease(void) { dbs("zoomIncrease") ; float dd = ZOOM_SCALE_STEP * szoom.scale; szoom.scale += (100) * dd; if( szoom.scale > ZOOM_SCALE_MAX) szoom.scale = ZOOM_SCALE_MAX; dbe("zoomIncrease") ; } void zoomDecrease(void) { dbs("zoomDecrease") ; float dd = ZOOM_SCALE_STEP * szoom.scale; szoom.scale += (-100) * dd; if( szoom.scale < ZOOM_SCALE_MIN) szoom.scale = ZOOM_SCALE_MIN; dbe("zoomDecrease") ; } void adjust(M_OnScreenDisplay* win, int width, int height){ int dw = win->getDesiredWinWidth() ; int dh = win->getDesiredWinHeight() ; if(dw < (float)width * OnScreenDisplay_PERCENTAGE_MIN) dw = (float)width * OnScreenDisplay_PERCENTAGE_MIN ; if(dh < (float)height * OnScreenDisplay_PERCENTAGE_MIN) dh = (float)height * OnScreenDisplay_PERCENTAGE_MIN ; if(dw > (float)width * OnScreenDisplay_PERCENTAGE_MAX) dw = (float)width * OnScreenDisplay_PERCENTAGE_MAX ; if(dh > (float)height * OnScreenDisplay_PERCENTAGE_MAX) dh = (float)height * OnScreenDisplay_PERCENTAGE_MAX ; win->setPosition( (float)width/2.0 - dw/2.0, (float)height/2.0 -dh/2.0); win->setSize( dw, dh); } void updateStatus(void) { m_status->setMapping( lumMappingName[bitmap->getMappingMethod()]); m_status->setMaxFreq( m_histogram->getMaxFrequency()); m_status->setFrameNo( bitmap->getFrameNo()); m_status->setChannel( bitmap->getVisibleChannel()); if(!szoom.pan) m_status->setNavMode("ZOOM"); else m_status->setNavMode("PAN"); m_status->setZoomScale(szoom.scale); } void refreshWinStat_PixelData(){ int x = cursorPosX ; int y = cursorPosY ; int curPX = cursorPosX ; int curPY = cursorPosY ; float val; glReadPixels( x, glutGet(GLUT_WINDOW_HEIGHT) - y, 1, 1, GL_RED, GL_FLOAT, &val); int r = (int)(val * 255); glReadPixels( x, glutGet(GLUT_WINDOW_HEIGHT) - y, 1, 1, GL_GREEN, GL_FLOAT, &val); int g = (int)(val * 255); glReadPixels( x, glutGet(GLUT_WINDOW_HEIGHT) - y, 1, 1, GL_BLUE, GL_FLOAT, &val); int b = (int)(val * 255); m_status->setPixelData( x, y, r, g, b); } void refreshWinStat_RawData(){ int xPosition, yPosition; int x, y ; if(!fix_cursor){ xPosition = cursorPosX - szoom.vX; // if(histogram->getFlag()) // yPosition = cursorPosY - (glutGet(GLUT_WINDOW_HEIGHT) - szoom.vY) -HISTOGRAM_HEIGHT/2 -Y_BAR/4; // else yPosition = cursorPosY - (glutGet(GLUT_WINDOW_HEIGHT) - szoom.vY) ; x = floor( xPosition / szoom.scale); y = floor( yPosition / szoom.scale); cursorPosX_fix = x; cursorPosY_fix = y; }else{ x = cursorPosX_fix ; y = cursorPosY_fix ; } float X, Y, Z; if((m_histogram->getSubComponentHoverState() == M_Histogram::NONE || fix_cursor) && !bitmap->getRawData( x, y, X, Y, Z)){ m_status->setRawData( x, y, X, Y, Z); m_histogram->setRawData( x, y, X, Y, Z); } else{ m_status->setRawData( -1, -1, 0, 0, 0); m_histogram->setRawData( -1, -1, 0, 0, 0); } } void updateHistogram(void) { dbs("updateHistogram") ; const char* ch = bitmap->getVisibleChannel(); m_histogram->setChannel(ch) ; m_histogram->setSliderPosMinMax( m_histogram->lum2pos(bitmap->getLumMin()) , m_histogram->lum2pos(bitmap->getLumMax())); dbe("updateHistogram") ; } void updateMapping_callback (){ //TODO macht menu kaputt if(glutGetWindow() != 0) glutPostRedisplay() ; } pthread_t updateMappingThread ; pthread_mutex_t updateMappingThread_count_mutex = PTHREAD_MUTEX_INITIALIZER; int updateMappingThread_count = 0 ; int updateMappingThread_running = 0 ; pthread_mutex_t updateMappingThread_update_mutex = PTHREAD_MUTEX_INITIALIZER; void *updateMapping_thread(void *argument) { // TODO hier bitte echte argumente uebergeben int wait_to_start = *((int*)argument) ; int thread_num ; pthread_mutex_lock( &updateMappingThread_count_mutex ); if(updateMappingThread_running < updateMappingThread_count) { // printf("RETURN %d %d\n", thread_running,thread_count); pthread_mutex_unlock( &updateMappingThread_count_mutex ); return (NULL); } thread_num = ++updateMappingThread_count; bitmap->abort_update = 1 ; pthread_mutex_unlock( &updateMappingThread_count_mutex ); //TODO argument if(wait_to_start > 0){ // printf("WAIT %d %d \n", thread_num, wait_to_start); usleep(wait_to_start * 1000) ; } int r = pthread_mutex_lock( &updateMappingThread_update_mutex ); pthread_mutex_lock( &updateMappingThread_count_mutex ); // printf("threadnum: %d \n", thread_num); // printf("threadcou: %d \n", thread_count); if(thread_num == updateMappingThread_count){ // printf("START %d %d\n", thread_num,thread_count); bitmap->abort_update = 0 ; updateMappingThread_running = thread_num ; pthread_mutex_unlock( &updateMappingThread_count_mutex ); // winMapping->setVisible(true); bitmap->updateMapping(updateMapping_callback) ; // winMapping->setVisible(false); updateHistogram() ; updateStatus(); updateMapping_callback() ; }else{ // printf("STOP %d %d\n", thread_num,thread_count); } pthread_mutex_unlock( &updateMappingThread_count_mutex ); pthread_mutex_unlock( &updateMappingThread_update_mutex ); return NULL; } void updateMapping(int* wait_to_start) { dbs("updateMapping") ; // char* string = {"normal run\n"}; // char* string = {"wait\n"}; pthread_create (&updateMappingThread, NULL, updateMapping_thread, wait_to_start); pthread_detach( updateMappingThread ); // bitmap->updateMapping(CB) ; // redrawHistogram() ; dbe("updateMapping") ; } void updateMapping() { dbs("updateMapping") ; // char* string = {"normal run\n"}; // char* string = {"wait\n"}; pthread_create (&updateMappingThread, NULL, updateMapping_thread, &wait_to_start); pthread_detach( updateMappingThread ); // bitmap->updateMapping(CB) ; // redrawHistogram() ; dbe("updateMapping") ; } /** Change mapping or redraw after changing luminance range. */ // not used anymore //void changeMapping( float lumMin, float lumMax) { // // // update slider // histogram->setSliderPosMinMax( histogram->lum2pos(lumMin), histogram->lum2pos(lumMax)); // bitmap->changeMapping( lumMin, lumMax); // updateMapping() ; //} void sliderMoveMax( int shift, int hwidth) { dbs("sliderMoveMax") ; m_histogram->setSliderPosMax( m_histogram->getSliderPosMax() + (float)shift / hwidth); bitmap->setMaxLum( m_histogram->pos2lum(m_histogram->getSliderPosMax())); dbe("sliderMoveMax") ; } void sliderMoveMin( int shift, int hwidth) { dbs("sliderMoveMin") ; m_histogram->setSliderPosMin( m_histogram->getSliderPosMin() + (float)shift / hwidth); bitmap->setMinLum( m_histogram->pos2lum(m_histogram->getSliderPosMin())); dbe("sliderMoveMin") ; } pthread_t computeHistogramThread ; pthread_mutex_t computeHistogramThread_count_mutex = PTHREAD_MUTEX_INITIALIZER; int computeHistogramThread_count = 0 ; int computeHistogramThread_running = 0 ; pthread_mutex_t computeHistogramThread_update_mutex = PTHREAD_MUTEX_INITIALIZER; int incdec = 0 ; void computeHistogram_callback (){ //TODO macht menu kaputt resetFMax = 1 ; if(glutGetWindow() != 0) glutPostRedisplay() ; } void *computeHistogram_thread(void *argument) { // TODO hier bitte echte argumente uebergeben int wait_to_start = *((int*)argument) ; int thread_num ; pthread_mutex_lock( &computeHistogramThread_count_mutex ); if(computeHistogramThread_running < computeHistogramThread_count) { // printf("RETURN %d %d\n", thread2_running,thread2_count); pthread_mutex_unlock( &computeHistogramThread_count_mutex ); return (NULL); } thread_num = ++computeHistogramThread_count; m_histogram->abort_update = 1 ; pthread_mutex_unlock( &computeHistogramThread_count_mutex ); //TODO argument if(wait_to_start > 0){ // printf("WAIT %d %d \n", thread2_num, wait_to_start); usleep(wait_to_start * 1000) ; } int r = pthread_mutex_lock( &computeHistogramThread_update_mutex ); pthread_mutex_lock( &computeHistogramThread_count_mutex ); // printf("threadnum: %d \n", thread2_num); // printf("threadcou: %d \n", thread2_count); if(thread_num == computeHistogramThread_count){ // printf("START %d %d\n", thread2_num,thread2_count); m_histogram->abort_update = 0 ; computeHistogramThread_running = thread_num ; pthread_mutex_unlock( &computeHistogramThread_count_mutex ); pthread_mutex_lock( &updateMappingThread_count_mutex ); bitmap->abort_update = 1 ; pthread_mutex_lock( &updateMappingThread_update_mutex ); if(incdec == 1) bitmap->gotoNextFrame(); if(incdec == -1) bitmap->gotoPreviousFrame(); pthread_mutex_unlock( &updateMappingThread_update_mutex ); pthread_mutex_unlock( &updateMappingThread_count_mutex ); zoomReset(); bitmap->setVisibleChannel( bitmap->CHANNEL_XYZ); bitmap->changeMapping( MAP_GAMMA2_2); m_histogram->clearChannel(0,computeHistogram_callback); m_histogram->clearChannel(1,computeHistogram_callback); m_histogram->clearChannel(2,computeHistogram_callback); m_histogram->clearChannel(3,computeHistogram_callback); m_histogram->clearChannel(4,computeHistogram_callback); m_osd_loading->setVisible(true); m_histogram->computeFrequency(0,bitmap->getPrimaryChannel(),computeHistogram_callback); channel = bitmap->getVisibleChannel() ; // histogram->resetFrequencyMax() ; resetHistogram(); // compute histogram for a given image m_histogram->computeFrequency(1, bitmap->getFrame()->getChannel("X"),computeHistogram_callback); m_histogram->computeFrequency(2, bitmap->getFrame()->getChannel("Y"),computeHistogram_callback); m_histogram->computeFrequency(3, bitmap->getFrame()->getChannel("Z"),computeHistogram_callback); m_histogram->computeFrequency(0, NULL,computeHistogram_callback); m_osd_loading->setVisible(false); // histogram->resetFrequencyMax() ; resetFMax = 1 ; // if(thread_count == 1){ resetHistogram(); // compute histogram for a given image // } // printf("END %d %d\n", thread2_num,thread2_count); }else{ // printf("STOP %d %d\n", thread2_num,thread2_count); } pthread_mutex_unlock( &computeHistogramThread_count_mutex ); pthread_mutex_unlock( &computeHistogramThread_update_mutex ); return NULL; } void computeHistogram() { dbs("computeHistogram") ; pthread_create (&computeHistogramThread, NULL, computeHistogram_thread, &wait_to_start); pthread_detach( computeHistogramThread ); dbe("computeHistogram") ; } /** Main display routine */ void display(void) { glClear(GL_COLOR_BUFFER_BIT); // If the picture was loaded we need to rescale it to window size if(bitmap != NULL) { GLint viewp[4]; glGetIntegerv( GL_VIEWPORT, viewp); glPixelStorei(GL_UNPACK_ALIGNMENT, 4); winWidth = glutGet(GLUT_WINDOW_WIDTH); winHeight = glutGet(GLUT_WINDOW_HEIGHT); szoom.vX = (int)(winWidth*0.5 - ((bitmap->getWidth() - szoom.x) * szoom.scale)*0.5); if(m_histogram->getVisible() == GL_TRUE) { szoom.vY = (int)(winHeight*0.5 + ((bitmap->getHeight() + szoom.y) * szoom.scale)*0.5 -m_histogram->getHeight()/2-Y_BAR/4) ; // szoom.vY = (int)(winHeight/2 + ((bitmap->getHeight() + szoom.y) * szoom.scale)/2 - HISTOGRAM_HEIGHT/2); } else { szoom.vY = (int)(winHeight*0.5 + ((bitmap->getHeight() + szoom.y) * szoom.scale)*0.5); } szoom.vSizeX = (int)(bitmap->getWidth() * szoom.scale); szoom.vSizeY = (int)(bitmap->getHeight() * szoom.scale); glEnable( GL_SCISSOR_TEST); if(m_histogram->getVisible() == GL_TRUE) { // glScissor( X_BAR/2, Y_BAR/2, winWidth - X_BAR, winHeight - Y_BAR ); glScissor( X_BAR/2, Y_BAR/2, winWidth - X_BAR, winHeight - Y_BAR - m_histogram->getHeight() - Y_BAR/2); } else { glScissor( X_BAR/2, Y_BAR/2, winWidth - X_BAR, winHeight - Y_BAR ); } /* glViewport(0 , bitmap->getHeight(), 0, 0); glPixelZoom( 1, -1); glRasterPos2i(0, 0); glDrawPixels(bitmap->getWidth(), bitmap->getHeight(), GL_RGBA, GL_UNSIGNED_BYTE, bitmap->getImageData() ); */ // if(histogram->getFlag() == GL_TRUE) { // glViewport( szoom.vX, szoom.vY-HISTOGRAM_HEIGHT/2-Y_BAR/4, szoom.vSizeX, szoom.vSizeY); // } // else{ glViewport( szoom.vX, szoom.vY, szoom.vSizeX, szoom.vSizeY); // } glPixelZoom( szoom.scale, -szoom.scale); glRasterPos2i(0,0); glDrawPixels(bitmap->getWidth(), bitmap->getHeight(), GL_RGBA, GL_UNSIGNED_BYTE, bitmap->getImageData() ); //printf("%d %d %d %d\n", szoom.vX, szoom.vY, szoom.vSizeX, szoom.vSizeY) ; // int nw = szoom.vSizeX ; // int nh = szoom.vSizeY ; // //// if(histogram->getFlag() == GL_TRUE) { //// glViewport( szoom.vX, szoom.vY-HISTOGRAM_HEIGHT/2-Y_BAR/4-(float)bitmap->getHeight()*szoom.scale, nw, nh); //// } //// else{ // glViewport( szoom.vX, szoom.vY-bitmap->getHeight()*szoom.scale, nw, nh); //// } // // // glEnable(GL_BLEND); // // // // glEnable (GL_TEXTURE_2D) ; // // glBindTexture(GL_TEXTURE_2D, 13); // glPixelStorei(GL_UNPACK_ALIGNMENT, 4); //// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); //// glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); // glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); // glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); // glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); // glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA, bitmap->getWidth(), bitmap->getHeight(), 0, GL_RGBA, GL_FLOAT, bitmap->getImageData()); // //// // //// // // int ww = bitmap->getWidth(); // int hh = bitmap->getHeight(); // float ar = ww / hh * 100; //// //// printf("%d %d %d %d\n", szoom.vSizeX, szoom.vSizeY, winWidth, winHeight ) ; //// //// //// // glBindTexture (GL_TEXTURE_2D, 13); //// glClearColor(1.0f, 1.0f, 1.0f, 1.0f); //// glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //// glLoadIdentity(); // //glColor4f(1.0f, 1.0f, 1.0f, 1.0f); // glBegin(GL_QUADS); // glTexCoord2f (0.0, 1.0); // glVertex3f(0,0 , -0.5f); // // glTexCoord2f (0.0, 0.0); // glVertex3f(0,100, -0.5f); // // glTexCoord2f (1.0, 0.0); // glVertex3f(ar,100, -0.5f); // // glTexCoord2f (1.0, 1.0); // glVertex3f(ar,0, -0.5f); // glEnd(); // // // glDisable (GL_TEXTURE_2D) ; // // glDisable(GL_BLEND); glViewport( viewp[0], viewp[1], viewp[2], viewp[3]); // // // int xPos, yPos; // xPos = cursorPosX - szoom.vX; // yPos = cursorPosY - (glutGet(GLUT_WINDOW_HEIGHT) - szoom.vY) -HISTOGRAM_HEIGHT/2 ; // // int xx = floor( xPos / szoom.scale); // int yy = floor( yPos / szoom.scale); float xPos, yPos; for (int i = 0 ; i < point_counter ; i++){ xPos = (points[i].x) * szoom.scale + szoom.vX; // if(histogram->getFlag()){ // yPos = glutGet(GLUT_WINDOW_HEIGHT) - ((points[i].y) * szoom.scale + (glutGet(GLUT_WINDOW_HEIGHT) - szoom.vY) +HISTOGRAM_HEIGHT/2 +Y_BAR/4 ) ; // } // else{ yPos = glutGet(GLUT_WINDOW_HEIGHT) - ((points[i].y) * szoom.scale + (glutGet(GLUT_WINDOW_HEIGHT) - szoom.vY) ) ; // } // only for BEAUTY, to center the dot in the middle of the cross cursor xPos=floor(xPos) ; yPos=floor(yPos) ; float mark_size = szoom.scale/4 ; if (mark_size < 6) { mark_size = 6; } // glColor4f(1.0f, 1.0f, 0.0f, 0.33f); // glBegin(GL_QUADS); // glVertex3f(xPos-mark_size, yPos-mark_size, -0.5f); // glVertex3f(xPos+mark_size, yPos-mark_size, -0.5f); // glVertex3f(xPos+mark_size, yPos+mark_size, -0.5f); // glVertex3f(xPos-mark_size, yPos+mark_size, -0.5f); // glEnd(); // glEnable(GL_BLEND); // glColor4f(1.0f, 0.0f, 0.0f, 0.8f); // glBegin(GL_LINES); // glVertex3f(xPos-mark_size, yPos-mark_size, -0.5f); // glVertex3f(xPos+mark_size, yPos+mark_size, -0.5f); // glVertex3f(xPos+mark_size, yPos-mark_size, -0.5f); // glVertex3f(xPos-mark_size, yPos+mark_size, -0.5f); // glEnd(); // glDisable(GL_BLEND); glColor4f(0.0f, 1.0f, 0.0f, 1.0f); glPointSize(2); glBegin(GL_POINTS); glVertex3f(xPos, yPos, -0.5f); glEnd(); glBegin(GL_LINE_LOOP); glVertex3f(xPos-mark_size, yPos-mark_size, -0.5f); glVertex3f(xPos+mark_size+1, yPos-mark_size, -0.5f); glVertex3f(xPos+mark_size+1, yPos+mark_size+1, -0.5f); glVertex3f(xPos-mark_size, yPos+mark_size+1, -0.5f); glEnd(); } glDisable( GL_SCISSOR_TEST); } refreshWinStat_RawData() ; refreshWinStat_PixelData() ; // Histogram drawing (and slider) if(m_histogram->getVisible() == GL_TRUE) { if(m_histogram != NULL) { if(resetFMax){ m_histogram->resetFrequencyMax() ; resetFMax = 0 ; } m_histogram->redraw(); } } m_status->redraw(); m_osd_help->redraw(); m_osd_loading->redraw(); m_osd_mapping->redraw(); glMatrixMode(GL_COLOR); glLoadIdentity(); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glFlush(); glutSwapBuffers(); } /** Resizes main window. */ void resizeWindow(int width, int height) { #ifdef __APPLE__ // "Think different" for OS/X :) glutReshapeWindow(width, height); #endif glViewport(0, 0, width, height); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(0.0f, width, 0.0f, height, -10.0f, 10.0f); // last 1.0 -1.0 it's enough glMatrixMode(GL_MODELVIEW); viewport.ySize = height; viewport.xSize = width; int newHeight = m_histogram->getHeight() ; if(m_histogram->getHeight() > height - INFOAREA_HEIGHT -Y_BAR - Y_BAR/2) newHeight = height - INFOAREA_HEIGHT -Y_BAR -Y_BAR/2; if(newHeight < 50) newHeight = 50 ; m_histogram->setSize(width - X_BAR, newHeight ) ; m_histogram->setPosition( X_BAR/2, height - Y_BAR/2 - m_histogram->getHeight()); adjust(m_osd_help, width, height) ; m_osd_loading->setPosition( glutGet(GLUT_WINDOW_WIDTH) - INFOAREA_WIDTH/2 - INFOAREA_POS_X, INFOAREA_POS_Y); m_osd_loading->setSize( INFOAREA_WIDTH/2, INFOAREA_HEIGHT); m_osd_loading->setBackgroundColor(0, 0, 0, 0.75) ; m_osd_mapping->setPosition( glutGet(GLUT_WINDOW_WIDTH) - INFOAREA_WIDTH/2 - INFOAREA_POS_X, INFOAREA_POS_Y); m_osd_mapping->setSize( INFOAREA_WIDTH/2, INFOAREA_HEIGHT); m_osd_mapping->setBackgroundColor(0, 0, 0, 0.75) ; glutPostRedisplay(); } void setCursor(int cursor){ if(cursor > 0){ glutSetCursor(cursor); } else { if(m_histogram->getVisible()){ m_histogram->processPointerPosition(cursorPosX, cursorPosY, szoom.pan); } if(!m_histogram->getVisible() || m_histogram->getSubComponentHoverState() == M_Histogram::NONE) { if(select_points) glutSetCursor(GLUT_CURSOR_CROSSHAIR); else if(szoom.pan) glutSetCursor(GLUT_CURSOR_INFO); else glutSetCursor(GLUT_CURSOR_LEFT_RIGHT); } } } /** Menu listener */ void menuListener(int menuId) { dbs("menuListener") ; int hwidth = m_histogram->getInnerWidth() ; switch(menuId) { case MENU_FIX_CURSOR: fix_cursor = !fix_cursor ; break ; case MENU_SELECT_POINTS: select_points = !select_points ; break; case MENU_ZOOM_IN: zoomIncrease(); updateStatus(); break; case MENU_ZOOM_OUT: zoomDecrease(); updateStatus(); break; case MENU_ZOOM_RESET: zoomReset(); bitmap->setVisibleChannel( bitmap->CHANNEL_XYZ); bitmap->changeMapping( MAP_GAMMA2_2); resetHistogram(); break; case MENU_HIST_RIGHT: sliderMoveMax( SLIDER_STEP, hwidth); sliderMoveMin( SLIDER_STEP, hwidth); updateMapping() ; break; case MENU_HIST_LEFT: sliderMoveMin( -SLIDER_STEP, hwidth); sliderMoveMax( -SLIDER_STEP, hwidth); updateMapping() ; break; case MENU_HIST_INCREASE: sliderMoveMin( -SLIDER_STEP, hwidth); sliderMoveMax( SLIDER_STEP, hwidth); updateMapping() ; break; case MENU_HIST_DECREASE: sliderMoveMin( SLIDER_STEP, hwidth); sliderMoveMax( -SLIDER_STEP, hwidth); updateMapping() ; break; case MENU_FRAME_NEXT: incdec = 1 ; setWindowTitle(); // TODO not safe because of running threads; // thread_count = 0 ; pthread_mutex_lock( &computeHistogramThread_count_mutex ); m_histogram->abort_update = 1 ; pthread_mutex_unlock( &computeHistogramThread_count_mutex ); computeHistogram() ; break; case MENU_FRAME_PREVIOUS: incdec = -1 ; setWindowTitle(); // TODO not safe ; // thread_count = 0 ; pthread_mutex_lock( &computeHistogramThread_count_mutex ); m_histogram->abort_update = 1 ; pthread_mutex_unlock( &computeHistogramThread_count_mutex ); computeHistogram() ; break; case MENU_HISTOGRAM : m_histogram->setVisible( !m_histogram->getVisible()); break; case MENU_INFO: m_status->setVisible( !m_status->getVisible()); break; case MENU_HELP: m_osd_help->setVisible( !m_osd_help->getVisible()); break; case MENU_MONITOR_LUM : bitmap->changeMapping( MONITOR_LUM_MIN, MONITOR_LUM_MAX); updateMapping() ; break; case MENU_MAX_LUM : float min, max; min = bitmap->computeLumMin(); max = bitmap->computeLumMax(); bitmap->changeMapping( min, max); updateMapping() ; break; case MENU_MAPPING_LINEAR: bitmap->changeMapping( MAP_LINEAR); updateMapping() ; break; case MENU_MAPPING_GAMMA_1_4: bitmap->changeMapping( MAP_GAMMA1_4); updateMapping() ; break; case MENU_MAPPING_GAMMA_1_8: bitmap->changeMapping( MAP_GAMMA1_8); updateMapping() ; break; case MENU_MAPPING_GAMMA_2_2: bitmap->changeMapping( MAP_GAMMA2_2); updateMapping() ; break; case MENU_MAPPING_GAMMA_2_6: bitmap->changeMapping( MAP_GAMMA2_6); updateMapping() ; break; case MENU_MAPPING_LOG: bitmap->changeMapping( MAP_LOGARITHMIC); updateMapping() ; break; case MENU_CHANNEL_XYZ: bitmap->setVisibleChannel( bitmap->CHANNEL_XYZ); updateMapping() ; channel = bitmap->getVisibleChannel() ; break; case MENU_CHANNEL_X: bitmap->setVisibleChannel( bitmap->CHANNEL_X); updateMapping() ; channel = bitmap->getVisibleChannel() ; break; case MENU_CHANNEL_Y: bitmap->setVisibleChannel( bitmap->CHANNEL_Y); updateMapping() ; channel = bitmap->getVisibleChannel() ; break; case MENU_CHANNEL_Z: bitmap->setVisibleChannel( bitmap->CHANNEL_Z); updateMapping() ; channel = bitmap->getVisibleChannel() ; break; case MENU_SAVE: // save current bitmap data (8-bits RGB) to stdout bitmap->save(); exit(0); break; case MENU_EXIT: exit(0); break; } setCursor(0) ; glutPostRedisplay(); dbe("menuListener") ; } void mouseListener(int button, int state, int x, int y) { dbs("mouseListener") ; // this one is a matter of taste // It changes the behaviour after mouse button release over the histogram. // If enabled, mouse over histogram is sufficient to trigger histogram. // If disabled first you have to move the mouse to enable histogram actions // after a button release over the histogram. // if(histogram->getFlag() == GL_TRUE) // histogram->processSliderSelection(x, y); cursorPosX = x; // required for mouse motion listener cursorPosY = y; // slider was moved, the bitmap should be recalculated if( m_histogram->getVisible() == GL_TRUE && state == GLUT_UP && (button != 3 && button != 4) && (m_histogram->getSubComponentHoverState() == M_Histogram::LEFT_BAR || m_histogram->getSubComponentHoverState() == M_Histogram::RIGHT_BAR || m_histogram->getSubComponentHoverState() == M_Histogram::WHOLE_SLIDER ) ) { // bitmap->changeMapping(); // updateMapping() ; // histogram->processSliderSelection(x, y); } if(button == GLUT_MIDDLE_BUTTON) { szoom.pan = !szoom.pan; updateStatus(); } if(select_points && button == GLUT_LEFT_BUTTON && state == GLUT_UP &&(cursorPosX_fixed == cursorPosX && cursorPosY_fixed == cursorPosY)) { float xPos, yPos; xPos = cursorPosX - szoom.vX; // if(histogram->getFlag()){ // yPos = cursorPosY - (glutGet(GLUT_WINDOW_HEIGHT) - szoom.vY) -HISTOGRAM_HEIGHT/2 -Y_BAR/4 ; // } // else{ yPos = cursorPosY - (glutGet(GLUT_WINDOW_HEIGHT) - szoom.vY) ; // } float xx = xPos / szoom.scale; float yy = yPos / szoom.scale; points[point_counter].x = xx ; points[point_counter].y = yy ; printf("point: %f %f\n", xx, yy) ; point_counter++ ; } // reset cursor position (for zooming) szoom.prevX = -1; szoom.prevY = -1; if((button == 3 || button == 4) && state == GLUT_DOWN && m_histogram->getSubComponentHoverState() == M_Histogram::NONE ) { //********************* float oxPos, oyPos; oxPos = (float)winWidth*0.5 - (((float)bitmap->getWidth() - szoom.x) * szoom.scale)*0.5; if(m_histogram->getVisible() == GL_TRUE) { oyPos = (float)winHeight*0.5 + (((float)bitmap->getHeight() + szoom.y) * szoom.scale)*0.5 -m_histogram->getHeight()/2-Y_BAR/4 ; } else { oyPos = (float)winHeight*0.5 + (((float)bitmap->getHeight() + szoom.y) * szoom.scale)*0.5; } float xPos, yPos; xPos = (float)cursorPosX - oxPos; yPos = (float)cursorPosY - (float) glutGet(GLUT_WINDOW_HEIGHT) + oyPos ; float xx = xPos / szoom.scale; float yy = yPos / szoom.scale; //---- float dd = ZOOM_SCALE_STEP * szoom.scale; if(button == 3) szoom.scale -= 50 * dd; else if(button == 4) szoom.scale += 50 * dd; if( szoom.scale < ZOOM_SCALE_MIN) szoom.scale = ZOOM_SCALE_MIN; if( szoom.scale > ZOOM_SCALE_MAX) szoom.scale = ZOOM_SCALE_MAX; //********************* oxPos = (float)cursorPosX - xx * szoom.scale ; szoom.x = ((2 * oxPos - (float)winWidth)/szoom.scale + (float)bitmap->getWidth()) ; oyPos = -(float)cursorPosY + (float) glutGet(GLUT_WINDOW_HEIGHT) + yy * szoom.scale ; if(m_histogram->getVisible() == GL_TRUE) { szoom.y = ((2.0 * oyPos - (float)winHeight +m_histogram->getHeight()+Y_BAR/2)/szoom.scale - (float)bitmap->getHeight()) ; }else{ szoom.y = ((2.0 * oyPos - (float)winHeight)/szoom.scale - (float)bitmap->getHeight()) ; } //---- updateStatus(); } if((button == 3 || button == 4) && state == GLUT_DOWN && m_histogram->getSubComponentHoverState() != M_Histogram::NONE ) { // float spos = (float)(cursorPosX-X_BAR-10)/(float)hwidth ; // printf("spos: %f\n", spos) ; // if(histogram->getSliderSelectionState() == BACK ){ //********************* float hwidth = (float) m_histogram->getInnerWidth() ; float vwidth = m_histogram->getVirtualWidth() ; float voff = m_histogram->getVirtualOffset() ; float rel_offset = ((vwidth-1.0)*hwidth)/(vwidth * hwidth)*voff + 1/vwidth*(((float)cursorPosX-X_BAR/2-m_histogram->getSidebarWidth())/hwidth) ; //----------------- float dd = ZOOM_SCALE_STEP * vwidth; if(button == 3) vwidth -= 25 * dd; else if(button == 4) vwidth += 25 * dd; if(vwidth > ZOOM_SCALE_MAX ) vwidth = ZOOM_SCALE_MAX ; else if(vwidth < 1) vwidth = 1; m_histogram->setVirtualWidth(vwidth) ; //********************* if(vwidth > 1){ voff = (rel_offset - 1/vwidth*(((float)cursorPosX-X_BAR/2-m_histogram->getSidebarWidth())/hwidth)) * (vwidth * hwidth)/((vwidth-1.0)*hwidth) ; if(voff<0 || rel_offset<0) voff = 0 ; if(voff>1 || rel_offset>1) voff = 1 ; m_histogram->setVirtualOffset(voff) ; } //----------------- // } // else{ // float dd = 1; // if(button == 3) dd *= -1; // // sliderMoveMin( (dd), vwidth*hwidth); // sliderMoveMax( -(dd), vwidth*hwidth); // bitmap->changeMapping(); // updateMapping() ; // } } setCursor(0) ; cursorPosX_fixed = x; // required for mouse motion listener cursorPosY_fixed = y; resizeWindow( glutGet(GLUT_WINDOW_WIDTH), glutGet(GLUT_WINDOW_HEIGHT) ); glutPostRedisplay(); dbe("mouseListener") ; } void mouseMotionListener(int x, int y) { bool is_control_down = false; // printf("XX %d\n", glutGetModifiers()) ; is_control_down = glutGetModifiers() & GLUT_ACTIVE_CTRL ; if(select_points && (cursorPosX_fixed != cursorPosX || cursorPosY_fixed != cursorPosY)){ // select_points = 0 ; setCursor(GLUT_CURSOR_INFO) ; // select_points = 1 ; } float hwidth = (float) m_histogram->getInnerWidth() ; float vwidth = m_histogram->getVirtualWidth() ; if(m_histogram->getVisible() == GL_TRUE) // slider { // printf("%d\n" , selectionState) ; switch(m_histogram->getSubComponentHoverState()) { case M_Histogram::LEFT_BAR: sliderMoveMin( x - cursorPosX, vwidth*hwidth); // cursorPosX = x; // glutSetCursor(GLUT_CURSOR_LEFT_SIDE); updateMapping(&pfs_wait) ; break; case M_Histogram::RIGHT_BAR: sliderMoveMax( x - cursorPosX, vwidth*hwidth); // cursorPosX = x; // glutSetCursor(GLUT_CURSOR_RIGHT_SIDE); updateMapping(&pfs_wait) ; break; case M_Histogram::WHOLE_SLIDER: case M_Histogram::BACK: if(is_control_down){ if(!szoom.pan){ sliderMoveMin( -(x - cursorPosX), vwidth*hwidth); sliderMoveMin( -(cursorPosY -y), vwidth*hwidth); sliderMoveMax( (x - cursorPosX), vwidth*hwidth); sliderMoveMax( (cursorPosY- y), vwidth*hwidth); }else{ if (x - cursorPosX < 0){ sliderMoveMin( x - cursorPosX, vwidth*hwidth); sliderMoveMax( x - cursorPosX, vwidth*hwidth); } else{ sliderMoveMax( x - cursorPosX, vwidth*hwidth); sliderMoveMin( x - cursorPosX, vwidth*hwidth); } } // cursorPosX = x; // glutSetCursor(GLUT_CURSOR_LEFT_RIGHT); updateMapping(&pfs_wait) ; break; }else{ } if(!szoom.pan){ //********************* // float hwidth = (float) histogram->getWidth() ; // float vwidth = histogram->getVirtualWidth() ; float voff = m_histogram->getVirtualOffset() ; float rel_offset = ((vwidth-1.0)*hwidth)/(vwidth * hwidth)*voff + 1/vwidth*(((float)cursorPosX_fixed-X_BAR/2-m_histogram->getSidebarWidth())/hwidth) ; if(rel_offset<0) rel_offset = 0 ; if(rel_offset>1) rel_offset = 1 ; //----------------- float dd = ZOOM_SCALE_STEP * vwidth; vwidth += (x-cursorPosX) * dd; vwidth += (cursorPosY-y) * dd; if(vwidth > ZOOM_SCALE_MAX ) vwidth = ZOOM_SCALE_MAX ; else if(vwidth < 1) vwidth = 1; m_histogram->setVirtualWidth(vwidth) ; //********************* if(vwidth > 1){ voff = (rel_offset - 1/vwidth*(((float)cursorPosX_fixed-X_BAR/2-m_histogram->getSidebarWidth())/hwidth)) * (vwidth * hwidth)/((vwidth-1.0)*hwidth) ; if(voff<0) voff = 0 ; if(voff>1) voff = 1 ; m_histogram->setVirtualOffset(voff) ; } //----------------- }else{ float offset = m_histogram->getVirtualOffset() ; offset -= (x-cursorPosX) * 1/((vwidth-1.0)*hwidth) ; if(offset > 1.0) offset = 1.0; else if(offset < -0.0) offset = 0.0; else if( isnan(offset) ) offset = 0.0; // printf("setting offset: %f\n", offset) ; m_histogram->setVirtualOffset(offset) ; } if(m_histogram->getSubComponentHoverState() == M_Histogram::WHOLE_SLIDER) break; case M_Histogram::BOTTOM: int newHeight = m_histogram->getHeight() + (y-cursorPosY) ; if(newHeight < 75) newHeight = 75 ; if(newHeight > glutGet(GLUT_WINDOW_HEIGHT) - INFOAREA_HEIGHT -Y_BAR - Y_BAR/2) newHeight = glutGet(GLUT_WINDOW_HEIGHT) - INFOAREA_HEIGHT -Y_BAR -Y_BAR/2; // cursorPosY = y; m_histogram->setHeight(newHeight) ; m_histogram->setPosition( X_BAR/2, glutGet(GLUT_WINDOW_HEIGHT) - Y_BAR/2 - m_histogram->getHeight()); //glutPostRedisplay(); break ; } } if(m_histogram->getSubComponentHoverState() == M_Histogram::NONE) { // zooming // if(y > (HISTOGRAM_HEIGHT + Y_BAR)) { if( szoom.prevX != -1){ if( szoom.pan) { float dd = ZOOM_MOVE_STEP / szoom.scale; szoom.x += ((x - szoom.prevX) * dd); szoom.y += ((szoom.prevY - y) * dd); // zoom_akku_x += ((x - szoom.prevX) * dd); // zoom_akku_y += ((szoom.prevY - y) * dd); // if(fabs(zoom_akku_x) > 1){ // szoom.x += (zoom_akku_x>0?floor(zoom_akku_x):ceil(zoom_akku_x)) ; // zoom_akku_x = zoom_akku_x - (zoom_akku_x>0?floor(zoom_akku_x):ceil(zoom_akku_x)) ; // } // if(fabs(zoom_akku_y) > 1){ // szoom.y += (zoom_akku_y>0?floor(zoom_akku_y):ceil(zoom_akku_y)) ; // zoom_akku_y = zoom_akku_y - (zoom_akku_y>0?floor(zoom_akku_y):ceil(zoom_akku_y)) ; // } } else { //********************* float oxPos, oyPos; oxPos = (float)winWidth*0.5 - (((float)bitmap->getWidth() - szoom.x) * szoom.scale)*0.5; if(m_histogram->getVisible() == GL_TRUE) { oyPos = (float)winHeight*0.5 + (((float)bitmap->getHeight() + szoom.y) * szoom.scale)*0.5 -m_histogram->getHeight()/2-Y_BAR/4 ; } else { oyPos = (float)winHeight*0.5 + (((float)bitmap->getHeight() + szoom.y) * szoom.scale)*0.5; } float xPos, yPos; xPos = (float)cursorPosX_fixed - oxPos; yPos = (float)cursorPosY_fixed - (float) glutGet(GLUT_WINDOW_HEIGHT) + oyPos ; float xx = xPos / szoom.scale; float yy = yPos / szoom.scale; //---- float dd = ZOOM_SCALE_STEP * szoom.scale; szoom.scale -= ( szoom.prevX - x) * dd; szoom.scale += ( szoom.prevY - y) * dd; if( szoom.scale < ZOOM_SCALE_MIN) szoom.scale = ZOOM_SCALE_MIN; if( szoom.scale > ZOOM_SCALE_MAX) szoom.scale = ZOOM_SCALE_MAX; //********************* oxPos = (float)cursorPosX_fixed - xx * szoom.scale ; szoom.x = ((2 * oxPos - (float)winWidth)/szoom.scale + (float)bitmap->getWidth()) ; oyPos = -(float)cursorPosY_fixed + (float) glutGet(GLUT_WINDOW_HEIGHT) + yy * szoom.scale ; if(m_histogram->getVisible() == GL_TRUE) { szoom.y = ((2.0 * oyPos - (float)winHeight +m_histogram->getHeight()+Y_BAR/2)/szoom.scale - (float)bitmap->getHeight()) ; }else{ szoom.y = ((2.0 * oyPos - (float)winHeight)/szoom.scale - (float)bitmap->getHeight()) ; } //---- updateStatus(); } } szoom.prevX = x; szoom.prevY = y; } cursorPosX = x; // required for mouse motion listener cursorPosY = y; glutPostRedisplay(); } /** Mouse passive listener. */ void mousePassiveMotionListener(int x, int y) { cursorPosX = x; cursorPosY = y; setCursor(0) ; glutPostRedisplay(); } /** Keyboard listener. */ void keyListener(unsigned char key, int x, int y) { dbs("keyListener") ; int menuFuncId = MENU_NONE; switch (key) { case '.': menuFuncId = MENU_ZOOM_IN; break; case ',': menuFuncId = MENU_ZOOM_OUT; break; case 'r': menuFuncId = MENU_ZOOM_RESET; break; case '=': menuFuncId = MENU_HIST_RIGHT;break; case '-': menuFuncId = MENU_HIST_LEFT; break; case ']': menuFuncId = MENU_HIST_INCREASE; break; case '[': menuFuncId = MENU_HIST_DECREASE; break; case 'h': menuFuncId = MENU_HISTOGRAM; break; case 'i': menuFuncId = MENU_INFO; break; case 12 : menuFuncId = MENU_MONITOR_LUM; break; case '\\': menuFuncId = MENU_MAX_LUM; break; case '1': menuFuncId = MENU_MAPPING_GAMMA_1_4; break; case '2': menuFuncId = MENU_MAPPING_GAMMA_1_8; break; case '3': menuFuncId = MENU_MAPPING_GAMMA_2_2; break; case '4': menuFuncId = MENU_MAPPING_GAMMA_2_6; break; case 'l': menuFuncId = MENU_MAPPING_LINEAR; break; case 'o': menuFuncId = MENU_MAPPING_LOG; break; case 'n': menuFuncId = MENU_FRAME_NEXT; break; case 'p': menuFuncId = MENU_FRAME_PREVIOUS; break; case ' ': szoom.pan = !szoom.pan; updateStatus(); szoom.prevX = -1; szoom.prevY = -1; menuFuncId = MENU_NONE; break; case 'c': pthread_mutex_lock( &updateMappingThread_count_mutex ); bitmap->abort_update = 1 ; pthread_mutex_unlock( &updateMappingThread_count_mutex ); if(strcmp(channel, "XYZ") == 0){ menuFuncId = MENU_CHANNEL_X; break; } else{ switch(channel[0]) { case 'X': menuFuncId = MENU_CHANNEL_Y; break; case 'Y': menuFuncId = MENU_CHANNEL_Z; break; case 'Z': menuFuncId = MENU_CHANNEL_XYZ; break; } } break ; case 'f': menuFuncId = MENU_FIX_CURSOR ; break ; case 'g': m_histogram->set_is_scale_gaussed(!m_histogram->get_is_scale_gaussed()) ; break ; case 'm': m_histogram->set_is_scale_show_max_hue(!m_histogram->get_is_scale_show_max_hue()) ; break ; case 'x': bitmap->set_is_show_clipping(!bitmap->get_is_show_clipping()) ; updateMapping(); break ; case 's': menuFuncId = MENU_SELECT_POINTS; break ; case 'q': case 27: exit(0); break; } menuListener(menuFuncId); dbe("keyListener") ; } void specialKeyListener(int key, int x, int y) { dbs("specialKeyListener") ; int menuFuncId = MENU_NONE; switch (key) { case GLUT_KEY_F1 : menuFuncId = MENU_HELP; break; break; case GLUT_KEY_LEFT : szoom.x -= PAN_STEP; glutPostRedisplay(); break; case GLUT_KEY_RIGHT: szoom.x += PAN_STEP; glutPostRedisplay(); break; case GLUT_KEY_UP: szoom.y += PAN_STEP; glutPostRedisplay(); break; case GLUT_KEY_DOWN : szoom.y -= PAN_STEP; glutPostRedisplay(); break; } menuListener(menuFuncId); dbe("specialKeyListener") ; } /** Loads the bitmap from disk. Display an error message if it doesn't load... */ void loadPicture() { dbs("loadPicture") ; bitmap = new PictureIO(MAP_GAMMA2_2, INIT_MIN_LUM, INIT_MAX_LUM); dbe("loadPicture") ; } /** */ void resetHistogram(void) { dbs("resetHistogram") ; float min, max; m_histogram->computeLumRange( min, max); // starting luminance range on the slider (computed based on the histogram shape) // mapping method bitmap->changeMapping( min, max); updateMapping() ; zoomReset(); dbe("resetHistogram") ; } /** Changes main window title. */ void setWindowTitle( void) { dbs("setWindowTitle") ; // set window title char title[2000]; sprintf(title, "PFS GLview v.1.2 %s %dx%d", bitmap->getCurrentFileName(), bitmap->getWidth(), bitmap->getHeight()); glutSetWindowTitle(title); dbe("setWindowTitle") ; } /** Parses command line. */ int pfsglview( int argc, char* argv[]) { dbs("pfsglview") ; static struct option cmdLineOptions[] = { { "help", no_argument, NULL, 'h' }, { "verbose", no_argument, NULL, 'v' }, { NULL, 0, NULL, 0 } }; int optionIndex = 0; while( 1 ) { int c = getopt_long (argc, argv, "hv", cmdLineOptions, &optionIndex); if( c == -1 ) break; switch( c ) { case 'h': fprintf( stderr, "pfsglview [--verbose] [--help]\nHigh dynamic range image viewer. Use within pfstools pipe (e.g. pfsin image.hdr | pfsglview).\n"); return 1; case 'v': verbose = true; break; } } dbe("pfsglview") ; return 0; } int once = 1 ; void idle(){ if(once){ // char* strings[] = {"compute histograms\n"}; computeHistogram() ; zoomReset(); once = 0 ; } usleep(1000) ; // glutPostRedisplay() ; } /** Main routine. */ int main( int argc, char* argv[] ) { dbs("main") ; if( pfsglview( argc, argv )) return 1; loadPicture(); m_histogram = new M_Histogram(); m_status = new SC_Status(); m_osd_help = new M_OnScreenDisplay(helptext, sizeof( helptext ) / sizeof( helptext[0] ) ); m_osd_loading = new M_OnScreenDisplay(loadtext, sizeof( loadtext ) / sizeof( loadtext[0] ) ); m_osd_mapping = new M_OnScreenDisplay(maptext, sizeof( maptext ) / sizeof( maptext[0] ) ); updateStatus(); glutInit(&argc, argv); glutInitWindowPosition(0.1 * glutGet(GLUT_SCREEN_WIDTH), 0.1 * glutGet(GLUT_SCREEN_HEIGHT)); glutInitWindowSize(0.8 * glutGet(GLUT_SCREEN_WIDTH), 0.8 * glutGet(GLUT_SCREEN_HEIGHT)); glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE); win = glutCreateWindow("PFS GLview v.1.2.1"); // glutPositionWindow(0.25 * glutGet(GLUT_SCREEN_WIDTH), 0.25 * glutGet(GLUT_SCREEN_HEIGHT) ); // glutReshapeWindow(0.5 * glutGet(GLUT_SCREEN_WIDTH), 0.5 * glutGet(GLUT_SCREEN_HEIGHT) ); m_histogram->setPosition( X_BAR/2, glutGet(GLUT_WINDOW_HEIGHT) - Y_BAR/2 - m_histogram->getHeight()); // m_histogram->setSize( glutGet(GLUT_WINDOW_WIDTH) , Y_BAR); // m_histogram->setWinSize( HISTOGRAM_WIDTH, HISTOGRAM_HEIGHT); m_status->setPosition( INFOAREA_POS_X, INFOAREA_POS_Y); m_status->setSize( INFOAREA_WIDTH, INFOAREA_HEIGHT); adjust(m_osd_help, glutGet(GLUT_WINDOW_WIDTH), glutGet(GLUT_WINDOW_HEIGHT)) ; m_osd_loading->setHasBorder(false) ; m_osd_loading->setPosition( glutGet(GLUT_WINDOW_WIDTH) - INFOAREA_WIDTH/2 - INFOAREA_POS_X, INFOAREA_POS_Y); m_osd_loading->setSize( INFOAREA_WIDTH/2, INFOAREA_HEIGHT); m_osd_mapping->setHasBorder(false) ; m_osd_mapping->setPosition( glutGet(GLUT_WINDOW_WIDTH) - INFOAREA_WIDTH/2 - INFOAREA_POS_X, INFOAREA_POS_Y); m_osd_mapping->setSize( INFOAREA_WIDTH/2, INFOAREA_HEIGHT); setWindowTitle(); // popup menu mappingSubmenu = glutCreateMenu(menuListener); glutAddMenuEntry("Gamma 1.4 (1)", MENU_MAPPING_GAMMA_1_4); glutAddMenuEntry("Gamma 1.8 (2)", MENU_MAPPING_GAMMA_1_8); glutAddMenuEntry("Gamma 2.2 (3)", MENU_MAPPING_GAMMA_2_2); glutAddMenuEntry("Gamma 2.6 (4)", MENU_MAPPING_GAMMA_2_6); glutAddMenuEntry("Linear (L)", MENU_MAPPING_LINEAR); glutAddMenuEntry("Logarithmic (O)", MENU_MAPPING_LOG); channelSubmenu = glutCreateMenu(menuListener); glutAddMenuEntry("XYZ", MENU_CHANNEL_XYZ); //std::vector vec = bitmap->getChannelNames(); //for(int i = 0; i < vec.size(); i ++) // glutAddMenuEntry(vec[i], MENU_CHANNEL+MENU_CHANNEL_SHIFT+i); glutAddMenuEntry("X", MENU_CHANNEL_X); glutAddMenuEntry("Y", MENU_CHANNEL_Y); glutAddMenuEntry("Z", MENU_CHANNEL_Z); mainMenu = glutCreateMenu(menuListener); glutAddMenuEntry("Zoom reset (r)", MENU_ZOOM_RESET); glutAddMenuEntry("Zoom in (.)", MENU_ZOOM_IN); glutAddMenuEntry("Zoom out (,)", MENU_ZOOM_OUT); glutAddMenuEntry("",MENU_SEPARATOR); glutAddMenuEntry("Increase exposure (=)", MENU_HIST_RIGHT); glutAddMenuEntry("Decrease exposure (-)", MENU_HIST_LEFT); glutAddMenuEntry("Extend dynamic range (])", MENU_HIST_INCREASE); glutAddMenuEntry("Shrink dynamic range ([)", MENU_HIST_DECREASE); glutAddMenuEntry("Low dynamic range (Ctrl-L)", MENU_MONITOR_LUM); glutAddMenuEntry("Fit to dynamic range (\\)", MENU_MAX_LUM); glutAddMenuEntry("",MENU_SEPARATOR); glutAddSubMenu("Choose channel", channelSubmenu); glutAddSubMenu("Mapping method", mappingSubmenu); glutAddMenuEntry("",MENU_SEPARATOR); glutAddMenuEntry("Next frame (n)", MENU_FRAME_NEXT); glutAddMenuEntry("Previous frame (p)", MENU_FRAME_PREVIOUS); glutAddMenuEntry("",MENU_SEPARATOR); glutAddMenuEntry("Histogram (h)", MENU_HISTOGRAM); glutAddMenuEntry("Info (i)", MENU_INFO); glutAddMenuEntry("Help (F1)", MENU_HELP); glutAddMenuEntry("",MENU_SEPARATOR); glutAddMenuEntry("Save&Quit", MENU_SAVE); glutAddMenuEntry("Quit (Q or Esc)", MENU_EXIT); glutAttachMenu(GLUT_RIGHT_BUTTON); float ratio = (float)bitmap->getHeight() / (float)bitmap->getWidth(); szoom.scale = 1.0f; // default scale coefficient szoom.scale_default = szoom.scale; zoomReset(); // viewport GLint viewportSize[4]; glGetIntegerv(GL_VIEWPORT, viewportSize); viewport.xPos = viewportSize[0]; viewport.yPos = viewportSize[1]; viewport.xSize = viewportSize[2]; viewport.ySize = viewportSize[3]; // mouse listeners glClearColor(0.5f, 0.5f, 0.5f, 1.0f); glutMouseFunc(mouseListener); glutMotionFunc(mouseMotionListener); glutPassiveMotionFunc( mousePassiveMotionListener); // keyboard listeners glutKeyboardFunc(keyListener); glutSpecialFunc(specialKeyListener); glutDisplayFunc(display); glutReshapeFunc(resizeWindow); glutIdleFunc(idle) ; resizeWindow( glutGet(GLUT_WINDOW_WIDTH), glutGet(GLUT_WINDOW_HEIGHT) ); glutMainLoop(); return 0; } pfstools-2.2.0/src/pfsglview/glenv.h0000664000701400070140000000273314105165614016147 0ustar rkm38rkm38 #ifndef __GLENV_H #define __GLENV_H #ifdef __APPLE__ // "Think different" for OS/X :) #include "GLUT/glut.h" #include "OPENGL/gl.h" #include "OPENGL/glu.h" #include "OPENGL/glext.h" #else #if defined(_WIN32) || defined(_WIN64) || defined(__CYGWIN__) #include #endif #include "GL/glut.h" #include "GL/gl.h" #include "GL/glu.h" #include "GL/glext.h" #endif // uncomment the define for a console live call graph // this will not work in all situations, since some code is executed concurrently //#define CALLGRAPH #include #ifdef CALLGRAPH extern int ident ; inline void dbs(std::string message) { for(int i = ident-1; i > 0 ; i--) ((i==1)?printf("\u251c\u2500\u2500"):printf("\u2502 ")); printf("\u252c\u2500"); printf("%s\n", message.c_str()); ident++; } inline void dbe(std::string message) { ident--; // comment out next three lines for more compact display for(int i = ident; i > 0 ; i--) ((i==1)?printf("\u2514\u2500\u2500"):printf("\u2502 ")); printf("\u2518"); printf("\n", message.c_str()); } #else inline void dbs(std::string message) {} inline void dbe(std::string message) {} #endif #endif pfstools-2.2.0/src/pfsglview/module.h0000664000701400070140000000346714105165614016326 0ustar rkm38rkm38/** * @brief PFS library - additional utilities * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2006 Radoslaw Mantiuk * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Radoslaw Mantiuk, * @author Oliver Barth */ #include "glenv.h" #ifndef MODULE_H #define MODULE_H // pixel width of the module border #define X_BAR 20 #define Y_BAR 20 class Module { private: GLint param[4]; protected: int isVisible; int pos_x; int pos_y; int width; int height; GLfloat* winBackgroundColor; void drawBackground(void); int redrawStart(void); void redrawEnd(void); public: Module(); ~Module(); void setPosition( int _x, int _y); void setSize(int _width, int _height); int getWidth(); int getHeight(); void setWidth(int _width); void setHeight(int _height); void setVisible(bool _isVisible); bool getVisible(void); // not used anymore // int processSelection(int xCoord, int yCoord); }; #endif pfstools-2.2.0/src/pfsglview/picture_io.cpp0000664000701400070140000004662514105165614017541 0ustar rkm38rkm38/** * @brief PFS library - additional utilities * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2006 Radoslaw Mantiuk * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * */ #include #include #include #include #include #include "glenv.h" #include "picture_io.h" //#define DEBUG #define min(x,y) ( (x)<(y) ? (x) : (y) ) #define max(x,y) ( (x)>(y) ? (x) : (y) ) #define PIXEL_ALIGMENT 4 // number of bytes occupied by one pixel extern bool verbose; inline int clamp( int val, int min, int max, int ret) { if(ret == 0){ if( val < min ) return min; if( val > max ) return max; }else if(ret == -1){ if( val < min ) return -1; if( val > max ) return -1; }else if(ret == -2){ if( val < min ) return -1; if( val > max ) return -2; } return val; } PictureIO::PictureIO(LumMappingMethod mappingMethod, float minLum, float maxLum) { dbs("PictureIO::PictureIO") ; currentFrame = frameList.end(); visibleChannel = CHANNEL_XYZ; imageMappingMethod = mappingMethod; minLuminance = minLum; maxLuminance = maxLum; pfsFrame = NULL; // current (active) pfsframe frameNo = 0; chR = chG = chB = NULL; CHANNEL_XYZ = "XYZ"; CHANNEL_X = "X"; CHANNEL_Y = "Y"; CHANNEL_Z = "Z"; is_show_clipping = 0 ; abort_update = 0; try { if( !readNextFrame() ) throw PFSglViewException(); else frameNo++; } catch( pfs::Exception ex ) { throw PFSglViewException(); } dbe("PictureIO::PictureIO") ; } PictureIO::~PictureIO() { delete pfsFrame; delete data; delete chR; delete chG; delete chB; } /** */ void PictureIO::setFrame(pfs::Frame *frame, const char *channel) { dbs("PictureIO::setFrame") ; if( frame == NULL) return; // only XYZ channels are taken into consideration if( strcmp( channel, "XYZ" ) && strcmp( channel, "X00" ) && strcmp( channel, "0Y0" ) && strcmp( channel, "00Z" ) ) { if(verbose) fprintf( stderr, "WARNING: wrong channel (PictureIO::setFrame())\n"); return; } if(pfsFrame != frame) { pfsFrame = frame; currentFileName = frame->getTags()->getString("FILE_NAME"); } visibleChannel = channel; pfs::Channel *X, *Y, *Z; pfsFrame->getXYZChannels( X, Y, Z ); if( X == NULL) { if(verbose) fprintf(stderr, "WARNING: No color channel avaible (PictureIO::setFrame())\n"); return; } width = X->getCols(); height = X->getRows(); if(data != NULL) delete data ; data = new unsigned char[width*height*PIXEL_ALIGMENT]; for(int i = 0 ; i < width * height * PIXEL_ALIGMENT ; i++){ data[i] = 128 ; } if(chR != NULL) delete chR ; if(chG != NULL) delete chG ; if(chB != NULL) delete chB ; chR = new pfs::Array2DImpl( width, height); chG = new pfs::Array2DImpl( width, height); chB = new pfs::Array2DImpl( width, height); pfs::transformColorSpace( pfs::CS_XYZ, X, Y, Z, pfs::CS_RGB, chR, chG, chB); // updateMapping(); dbe("PictureIO::setFrame") ; } void PictureIO::updateMapping( void CB(void)) { dbs("PictureIO::updateMapping") ; #define LUMSIZE 258 // const pfs::Array2D* ch = getPrimaryChannel(); // pfs::Channel *X, *Y, *Z; // pfsFrame->getXYZChannels( X, Y, Z ); float minValue = minLuminance; float maxValue = maxLuminance; pfs::Array2D* ch = chG; if( visibleChannel != CHANNEL_XYZ) { if( visibleChannel == CHANNEL_X) ch = chR; else if( visibleChannel == CHANNEL_Y) ch = chG; else if( visibleChannel == CHANNEL_Z) ch = chB; } // printf("f: %f %f\n", minLuminance, maxLuminance) ; int rows = ch->getRows(); int cols = ch->getCols(); float lumPixFloor[LUMSIZE]; for( int p = 1; p < LUMSIZE; p++ ) { float p_left = ((float)p - 1.f)/255.f; // Should be -1.5f, but we don't want negative nums lumPixFloor[p] = getInverseMapping( imageMappingMethod, p_left, minLuminance, maxLuminance ); } int index = 0; float ranger ; switch( imageMappingMethod ) { case MAP_GAMMA1_4: ranger = powf(1 / (maxLuminance-minLuminance), 1/1.4) * 255 ; break ; case MAP_GAMMA1_8: ranger = powf(1 / (maxLuminance-minLuminance), 1/1.8) * 255 ; break ; case MAP_GAMMA2_2: ranger = powf(1 / (maxLuminance-minLuminance), 1/2.2) * 255 ; break ; case MAP_GAMMA2_6: ranger = powf(1 / (maxLuminance-minLuminance), 1/2.6) * 255 ; break ; case MAP_LINEAR: ranger = 1 / (maxLuminance-minLuminance) * 255.0 ; break ; case MAP_LOGARITHMIC: ranger = 1 / (log10f(maxLuminance)-log10f(minLuminance)) * 255 ; minValue = log10f(minLuminance) ; break ; default: ranger = 1 ; break ; // assert(0); } int (PictureIO:: *callback) ( LumMappingMethod , float , float , float , float ) ;//= fastCheck ; callback = &PictureIO::fastMap ; if(maxLuminance == minLuminance) callback = &PictureIO::fastCheck ; #pragma omp parallel for private (index) for( int r = 0; r < rows; r++){ for( int c = 0; c < cols; c++) { if(abort_update == 1) continue ; // Color channels int pr, pg, pb; if( visibleChannel == CHANNEL_XYZ) { if(imageMappingMethod == MAP_LINEAR || maxLuminance == minLuminance){ pr = (this->*callback)(imageMappingMethod, (*chR)(c, r),minValue, maxValue, ranger) ; pg = (this->*callback)(imageMappingMethod, (*chG)(c, r),minValue, maxValue, ranger) ; pb = (this->*callback)(imageMappingMethod, (*chB)(c, r),minValue, maxValue, ranger) ; }else{ pr = binarySearchPixels( (*chR)(c, r), lumPixFloor, LUMSIZE ); pg = binarySearchPixels( (*chG)(c, r), lumPixFloor, LUMSIZE ); pb = binarySearchPixels( (*chB)(c, r), lumPixFloor, LUMSIZE ); } if(is_show_clipping){ int cpr = clamp( pr, 1, 256, -2 ); int cpg = clamp( pg, 1, 256, -2 ); int cpb = clamp( pb, 1, 256, -2 ); pr = clamp( pr, 1, 256, 0 ); pg = clamp( pg, 1, 256, 0 ); pb = clamp( pb, 1, 256, 0 ); // at least one above OR one below exposure if((cpr == -1 || cpg == -1 || cpb == -1) ||(cpr == -2 || cpg == -2 || cpb == -2)){ // printf("above %f:\n", (*chR)(c, r)) ; pr = 1 ; pg = 256 ; pb = 1 ; } // at least one above AND one below exposure if((cpr == -1 || cpg == -1 || cpb == -1) &&(cpr == -2 || cpg == -2 || cpb == -2)){ // printf("above %f:\n", (*chR)(c, r)) ; pr = 256 ; pg = 1 ; pb = 256 ; } // ALL below exposure if(cpr == -1 && cpg == -1 && cpb == -1){ // printf("above %f:\n", (*chR)(c, r)) ; pr = 256 ; pg = 1 ; pb = 1 ; } // ALL above exposure if(cpr == -2 && cpg == -2 && cpb == -2){ // printf("above %f:\n", (*chR)(c, r)) ; pr = 1 ; pg = 1 ; pb = 256 ; } }else{ pr = clamp( pr, 1, 256, 0 ); pg = clamp( pg, 1, 256, 0 ); pb = clamp( pb, 1, 256, 0 ); } } else { if(imageMappingMethod == MAP_LINEAR || maxLuminance == minLuminance){ pr = (this->*callback)(imageMappingMethod, (*ch)(c, r),minValue, maxValue, ranger) ; }else{ pr = binarySearchPixels( (*ch)(c, r), lumPixFloor, LUMSIZE ); } if(is_show_clipping){ int cpr = clamp( pr, 1, 256, -2 ); pr = clamp( pr, 1, 256, 0 ); pg = pr ; pb = pr ; // below exposure if(cpr == -1){ pr = 256 ; pg = 1 ; pb = 1 ; } // above exposure if(cpr == -2){ pr = 1 ; pg = 1 ; pb = 256 ; } } else { pr = clamp( pr, 1, 256, 0 ); pg = pr ; pb = pr ; } } index = r * (cols * PIXEL_ALIGMENT) + c * PIXEL_ALIGMENT; data[index] = pr-1; data[index + 1] = pg-1; data[index + 2] = pb-1; } if(r % 10 == 0) CB() ; } dbe("PictureIO::updateMapping") ; } /** Changes mapping method. */ void PictureIO::changeMapping( LumMappingMethod mappingMethod, float minLum, float maxLum) { if( imageMappingMethod == mappingMethod && minLum == minLuminance && maxLum == maxLuminance) return; imageMappingMethod = mappingMethod; minLuminance = minLum; maxLuminance = maxLum; // updateMapping(); } void PictureIO::changeMapping( LumMappingMethod mappingMethod) { if( imageMappingMethod == mappingMethod) return; imageMappingMethod = mappingMethod; // updateMapping(); } void PictureIO::changeMapping( float minLum, float maxLum) { if( minLum == minLuminance && maxLum == maxLuminance) return; minLuminance = minLum; maxLuminance = maxLum; // updateMapping(); } void PictureIO::changeMapping( void) { // updateMapping(); } /** * Searching for the closest pixel index (m) of a given luminance (lum) in the luminance * mapping table - lumMap. */ inline int PictureIO::binarySearchPixels( const float lum, const float *lumMap, const int lumSize ) { if(lum > lumMap[lumSize-1]) return lumSize-1 ; if(lum <= lumMap[1]) return 0 ; int l = 0, r = lumSize; while( true ) { int m = (l+r)/2; if( m == l ) break; if( lum < lumMap[m] ) r = m; else l = m; } return l; } /** * Searching for the closest pixel index (m) of a given luminance (lum) in the luminance * mapping table - lumMap. */ float PictureIO::getInverseMapping( LumMappingMethod mappingMethod, float v, float minValue, float maxValue ) { switch( mappingMethod ) { case MAP_GAMMA1_4: return powf(v, 1.4)*(maxValue-minValue) + minValue; case MAP_GAMMA1_8: return powf(v, 1.8)*(maxValue-minValue) + minValue; case MAP_GAMMA2_2: return powf(v, 2.2)*(maxValue-minValue) + minValue; case MAP_GAMMA2_6: return powf(v, 2.6)*(maxValue-minValue) + minValue; case MAP_LINEAR: return v*(maxValue-minValue) + minValue; case MAP_LOGARITHMIC: return powf(10, v * (log10f(maxValue) - log10f(minValue)) + log10f(minValue)); default: assert(0); return 0; } } inline int PictureIO::fastCheck( LumMappingMethod mappingMethod, float v, float minValue, float maxValue, float ranger) { if(v > maxLuminance) return 257 ; return 0 ; } inline int PictureIO::fastMap( LumMappingMethod mappingMethod, float v, float minValue, float maxValue, float ranger) { if(v > maxLuminance) return 257 ; if(v <= minLuminance) return 0 ; switch( mappingMethod ) { case MAP_GAMMA1_4: return floor(powf((v-minValue), 1/1.4) * ranger + 1) ; case MAP_GAMMA1_8: return floor(powf((v-minValue), 1/1.8) * ranger + 1) ; case MAP_GAMMA2_2: return floor(powf((v-minValue), 1/2.2) * ranger + 1) ; case MAP_GAMMA2_6: return floor(powf((v-minValue), 1/2.6) * ranger + 1) ; case MAP_LINEAR: return floor((v - minValue) * ranger + 1) ; case MAP_LOGARITHMIC: return floor( (log10f(v)-minValue) * ranger + 1) ; default: assert(0); return 0; } } /** * GUI action -> goto next frame * handle error and eof of frame */ void PictureIO::gotoNextFrame() { try { if(readNextFrame()) frameNo++; } catch( pfs::Exception ex ) { // Display message and keep the old frame fprintf(stderr, "pfsGLview error - exiting\n"); exit(-1); } } void PictureIO::gotoPreviousFrame() { currentFrame++; if( currentFrame == frameList.end() ) { currentFrame--; //fprintf(stderr, "No more frames in buffer (buffer holds max 5 frames)\n"); return; } setFrame( *currentFrame, CHANNEL_XYZ); frameNo--; } bool PictureIO::hasColorChannels( pfs::Frame *frame ) { if( frame == NULL ) frame = pfsFrame; assert( frame != NULL ); pfs::Channel *X, *Y, *Z; frame->getXYZChannels( X, Y, Z ); return ( X != NULL ); } const pfs::Array2D* PictureIO::getPrimaryChannel(void) { assert( pfsFrame != NULL ); if( visibleChannel == CHANNEL_XYZ ) { pfs::Channel *X, *Y, *Z; pfsFrame->getXYZChannels( X, Y, Z ); return Y; } else { return pfsFrame->getChannel( visibleChannel ); } } int PictureIO::getWidth() { return width; } int PictureIO::getHeight() { return height; } int PictureIO::getPixel(int ch, int c, int r) { if( c < 0 || c >= width || r < 0 || r >= height) { //fprintf( stderr, "WARNING: pixel position out of range (PictureIO::getPixel())\n"); return -1; } int index = r * (width * PIXEL_ALIGMENT) + c * PIXEL_ALIGMENT; return (int)data[index + ch]; } int PictureIO::getPixelR(int c, int r) { return getPixel(0, c, r); } int PictureIO::getPixelG(int c, int r) { return getPixel(1, c, r); } int PictureIO::getPixelB(int c, int r) { return getPixel(2, c, r); } int PictureIO::getRawData( int x, int y, float& XX, float& YY, float& ZZ) { if( x < 0 || y < 0 || x >=width || y >= height || pfsFrame == NULL) { if(verbose) fprintf( stderr, "WARNING: wrong pixel coordinates (PictureIO::getRawData())\n"); return 1; } pfs::Channel *X, *Y, *Z; pfsFrame->getXYZChannels( X, Y, Z ); if( X == NULL) { if(verbose) fprintf(stderr, "WARNING: No color channel avaible (PictureIO::getRawData())\n"); return 1; } XX = (*chR)( x,y); YY = (*chG)( x,y); ZZ = (*chB)( x,y); return 0; } const char *PictureIO::getCurrentFileName() { return currentFileName; } const char *PictureIO::getVisibleChannel() { return visibleChannel; } void PictureIO::setVisibleChannel(const char *channel) { if( channel == CHANNEL_XYZ || channel == CHANNEL_X || channel == CHANNEL_Y || channel == CHANNEL_Z) visibleChannel = channel; else if(verbose) fprintf( stderr, "WARNING: wrong channel name (%s) (PictureIO::setVisibleChannel())\n", channel); } pfs::Frame *PictureIO::getFrame() { return pfsFrame; } /** Saves bitmap data to stdout in pfstools format. */ // gamma correction for MAC #define GAMMA_CORRECTION 1.8f int PictureIO::save(void){ pfs::DOMIO io; pfs::Frame* frame = io.createFrame( width, height); pfs::Channel *X = frame->createChannel( "X"); pfs::Channel *Y = frame->createChannel( "Y"); pfs::Channel *Z = frame->createChannel( "Z"); frame->createXYZChannels( X, Y, Z); for(int j = 0; j < (width * height); j++){ (*X)(j) = powf((float)data[j*PIXEL_ALIGMENT] / 256.0f, GAMMA_CORRECTION); (*Y)(j) = powf((float)data[j*PIXEL_ALIGMENT+ 1] / 256.0f, GAMMA_CORRECTION); (*Z)(j) = powf((float)data[j*PIXEL_ALIGMENT + 2] / 256.0f, GAMMA_CORRECTION); } pfs::transformColorSpace( pfs::CS_RGB, X, Y, Z, pfs::CS_XYZ, X, Y, Z ); io.writeFrame( frame, stdout ); io.freeFrame( frame ); return 0; } /** Returns minimum luminance. */ float PictureIO::computeLumMin(void) { float valMin = 1e30; if( visibleChannel == CHANNEL_XYZ) { pfs::Channel *X, *Y, *Z; pfsFrame->getXYZChannels( X, Y, Z ); const int size = Y->getRows() * Y->getCols(); for( int i = 0; i < size; i++ ) { float val = (*X)(i); if( val < valMin) valMin = val; val = (*Y)(i); if( val < valMin) valMin = val; val = (*Z)(i); if( val < valMin) valMin = val; } return valMin; } const pfs::Array2D* image = getPrimaryChannel(); const int size = image->getRows() * image->getCols(); for( int i = 0; i < size; i++ ) { float val = (*image)(i); if( val < valMin) valMin = val; } return valMin; } /** Returns maximum luminance. */ float PictureIO::computeLumMax(void) { float valMax = -1e30; if( visibleChannel == CHANNEL_XYZ) { pfs::Channel *X, *Y, *Z; pfsFrame->getXYZChannels( X, Y, Z ); const int size = Y->getRows() * Y->getCols(); for( int i = 0; i < size; i++ ) { float val = (*X)(i); if( val > valMax) valMax = val; val = (*Y)(i); if( val > valMax) valMax = val; val = (*Z)(i); if( val > valMax) valMax = val; } return valMax; } const pfs::Array2D* image = getPrimaryChannel(); const int size = image->getRows() * image->getCols(); for( int i = 0; i < size; i++ ) { float val = (*image)(i); if( val > valMax) valMax = val; } return valMax; } /** Returns maximum luminance. */ float PictureIO::getLumMax(void) { return maxLuminance; } float PictureIO::getLumMin(void) { return minLuminance; } /** * Load next frame from the stream. * @return false if there are no more frames */ bool PictureIO::readNextFrame() { dbs("PictureIO::readNextFrame") ; if( currentFrame != frameList.begin() ) { currentFrame--; setFrame( *currentFrame, "XYZ" ); return true; } if( frameList.size() == MAX_FRAMES_IN_MEMORY ) { #ifdef DEBUG fprintf(stderr, "No memory for the next frame.\n"); #endif return false; } pfs::DOMIO pfsCtx; pfs::Frame *newFrame; #ifdef DEBUG fprintf(stderr, "Reading PFS Frame\n"); #endif newFrame = pfsCtx.readFrame( stdin ); if( newFrame == NULL ) { #ifdef DEBUG fprintf(stderr, "No more frames\n"); #endif return false; // No more frames } frameList.push_front( newFrame ); currentFrame = frameList.begin(); setFrame( newFrame, "XYZ" ); #ifdef DEBUG fprintf(stderr, "Number of frame: %d\n", frameList.size()); #endif dbe("PictureIO::readNextFrame") ; return true; } /** Returns current frame number. */ int PictureIO::getFrameNo(void) { return frameNo; } /** */ float PictureIO::getDynamicRange(void) { float valMin = computeLumMin(); float valMax = computeLumMax(); unsigned int max = (unsigned int)pow( 2, 32); printf(" lumMax:%f lumMin:%f max:%d dr:%f\n", valMax, valMin, max, log10(valMax/valMin)); const pfs::Array2D* image = getPrimaryChannel(); int size = image->getRows() * image->getCols(); float fval; unsigned int val; std::vector vec; for( int i = 0; i < size; i++ ) { fval = (*image)(i); fval /= valMax; val = (unsigned int)(fval * max); vec.push_back(val); } std::sort(vec.begin(), vec.end()); printf("size: %d\n", (int)vec.size()); val = vec[0]; std::vector svec; svec.push_back(val); for( int i = 1; i < vec.size(); i++) { if( vec[i] != val) { svec.push_back(vec[i]); val = vec[i]; } } float dr = svec[ svec.size()-1] / svec[0]; printf("min:%ld max:%ld svec size: %d dr:%f\n", (long)vec[0], (long)vec[ vec.size()-1], (int)svec.size(), log10(dr)); return valMax; } /** */ std::vector PictureIO::getChannelNames() { std::vector vec; if( pfsFrame == NULL) return vec; pfs::ChannelIterator *it = pfsFrame->getChannels(); while(it->hasNext() ) vec.push_back( it->getNext()->getName()); return vec; } /** */ void PictureIO::setMinLum( float val) { minLuminance = val; } /** */ void PictureIO::setMaxLum( float val) { maxLuminance = val; } /** */ void PictureIO::setMappingMethod( LumMappingMethod val) { imageMappingMethod = val; } /** */ LumMappingMethod PictureIO::getMappingMethod( void) { return imageMappingMethod; } /** */ unsigned char* PictureIO::getImageData( void) { return data; } int PictureIO::get_is_show_clipping() const { return is_show_clipping; } void PictureIO::set_is_show_clipping(int is_show_clipping) { this->is_show_clipping = is_show_clipping; } pfstools-2.2.0/src/octave/0002775000701400070140000000000014105165614014133 5ustar rkm38rkm38pfstools-2.2.0/src/octave/pfstransform_colorspace.help0000664000701400070140000000066314105165614021746 0ustar rkm38rkm38Tranform between color spaces using pfs library. usage: [C1 C2 C2] = pfstranform_colorspace( inCSname, c1, c2, c3, outCSname ); inCSname - name of the input color space c - matrix with n-th channel of input color space C - matrix with n-th channel of output color space outCSname - name of the output color space Recognized color space names: 'XYZ', 'RGB', 'sRGB', 'YUV', 'Yxy'. Color space names are case insensitive. pfstools-2.2.0/src/octave/pfs_read_luminance.m0000664000701400070140000000117014105165614020124 0ustar rkm38rkm38function Y = pfs_read_luminance( fileName ) ## Read hdr image file (formats accepted by pfsin) and return ## luminance channel Y. ## ## Y = hdr_read_luminance( fileName ) ## Check if file exists fid = fopen( fileName, "rb" ); if( fid == -1 ) error( sprintf( "pfs_read_luminance: File '%s' does not exist", fileName ) ); endif fclose( fid ); unwind_protect fid = popen( sprintf( "pfsin %s", fileName ), "r" ); pin = pfsopen( fid ); pin = pfsget( pin ); Y = pin.channels.Y; unwind_protect_cleanup pfsclose( pin ); fclose( fid ); end_unwind_protect endfunction pfstools-2.2.0/src/octave/pfs_read_xyz.m0000664000701400070140000000123314105165614017003 0ustar rkm38rkm38function [X Y Z] = pfs_read_xyz( fileName ) ## Read hdr image file (formats accepted by pfsin) and return X, Y, ## and Z color channels. ## ## [X Y Z] = hdr_read_rgb( fileName ) ## Check if file exists fid = fopen( fileName, "rb" ); if( fid == -1 ) error( sprintf( "pfs_read_xyz: File '%s' does not exist", fileName ) ); endif fclose( fid ); unwind_protect fid = popen( sprintf( "pfsin %s", fileName ), "r" ); pin = pfsopen( fid ); pin = pfsget( pin ); X = pin.channels.X; Y = pin.channels.Y; Z = pin.channels.Z; unwind_protect_cleanup pfsclose( pin ); fclose( fid ); end_unwind_protect pfstools-2.2.0/src/octave/pfsopen.cpp0000664000701400070140000001105014105165614016304 0ustar rkm38rkm38/** * @brief Open pfs stream for reading or writing in GNU Octave * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Rafal Mantiuk and Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Rafal Mantiuk, * * $Id: pfsopen.cpp,v 1.5 2008/05/06 18:01:32 rafm Exp $ */ #include #include #include //#include "../../config.h" // conflicts with config.h from octave distribution #include #include #include #include #define SCRIPT_NAME "pfsopen" static const char *helpString = #include "pfsopen_help.h" DEFUN_DLD( pfsopen, args, , helpString) { octave_value_list retval; int nargin = args.length(); // Get arguments and check if they are legal if( nargin < 1 || nargin > 3 ) { error( SCRIPT_NAME ": Improper usage!"); return retval; } if( !args(0).is_string() && !args(0).is_real_scalar() ) { error( SCRIPT_NAME ": expected file name or file descriptor as the first argument!"); // file descriptors are represented as integers (stored as doubles) in Octave 3.0 return retval; } int width, height; bool writeMode = false; if( nargin == 3 ) { if( !args(1).is_real_scalar() || !args(2).is_real_scalar() ) { error( SCRIPT_NAME ": expected frame dimmensions as argument 2 and 3"); return retval; } height = (int)args(1).double_value(); width = (int)args(2).double_value(); writeMode = true; } if( nargin == 2 ) { if( !args(1).is_real_matrix() ) { error( SCRIPT_NAME ": expected matrix as argument 2"); return retval; } Matrix dim = args(1).matrix_value(); if( dim.rows() != 1 || dim.columns() != 2 ) { error( SCRIPT_NAME ": expected 1x2 matrix with frame size as argument 2"); return retval; } height = (int)dim(0,0); width = (int)dim(0,1); writeMode = true; } if( writeMode && (width < 1 || height < 1 || width > 65535 || height > 65535 ) ) { error( SCRIPT_NAME ": Illegal frame size"); return retval; } // Open file for reading or writing FILE *fh; if( args(0).is_string() ) { // File name given const char *fileName = args(0).string_value().c_str(); if( writeMode ) { if( !strcasecmp( "stdout", fileName ) ) fh = stdout; else { fh = fopen( fileName, "wb" ); if( fh == NULL ) { error( SCRIPT_NAME ": cannot open file for writing!"); return retval; } } } else { if( !strcasecmp( "stdin", fileName ) ) fh = stdin; else { fh = fopen( fileName, "rb" ); if( fh == NULL ) { error( SCRIPT_NAME ": cannot open file for reading!"); return retval; } } } } else { // File descriptor given int fd = dup( (int) args(0).scalar_value() ); if( writeMode ) { fh = fdopen( fd, "wb" ); if( fh == NULL ) { error( SCRIPT_NAME ": cannot open file for writing!"); return retval; } } else { fh = fdopen( fd, "rb" ); if( fh == NULL ) { error( SCRIPT_NAME ": cannot open file for reading!"); return retval; } } } octave_scalar_map pfsStream; pfsStream.assign( "FH", octave_value((double)((long)fh)) ); pfsStream.assign( "MODE", writeMode ? octave_value("W") : octave_value("R") ); pfsStream.assign( "EOF", octave_value(false) ); if( writeMode ) { pfsStream.assign( "columns", octave_value(width) ); pfsStream.assign( "rows", octave_value(height) ); octave_scalar_map channels; pfsStream.assign( "channels", octave_value(channels) ); } retval.append(pfsStream); return retval; } pfstools-2.2.0/src/octave/pfswrite.help0000664000701400070140000000063114105165614016646 0ustar rkm38rkm38DEPRECIATED! Use pfsopen/pfsput/pfsclose instead Write matrices as channels in PFS format to stdout. usage: pfswrite( fileName, c1n, C1, c2n, C2, ...); fileName - name of the file to write, or \"stdout\" for standard output c1n - name of the first channel C1 - matrix with the first channel c2n - name of the second channel C2 - matrix with the second channel Channel names are case sensitive pfstools-2.2.0/src/octave/pfs_read_rgb.m0000664000701400070140000000131214105165614016721 0ustar rkm38rkm38function [R G B] = pfs_read_rgb( fileName ) ## Read hdr image file (formats accepted by pfsin) and return R, G, ## and B color channels. ## ## [R G B] = hdr_read_rgb( fileName ) ## Check if file exists fid = fopen( fileName, "rb" ); if( fid == -1 ) error( sprintf( "pfs_read_rgb: File '%s' does not exist", fileName ) ); endif fclose( fid ); unwind_protect fid = popen( sprintf( 'pfsin %s', fileName ), 'r' ); pin = pfsopen( fid ); pin = pfsget( pin ); [R G B] = pfstransform_colorspace( 'XYZ', pin.channels.X, pin.channels.Y, pin.channels.Z, 'RGB' ); unwind_protect_cleanup pfsclose( pin ); fclose( fid ); end_unwind_protect endfunction pfstools-2.2.0/src/octave/pfsoctavelum0000664000701400070140000000435414105165614016572 0ustar rkm38rkm38#!/usr/bin/octave -q # # This file is a part of PFSTOOLS package. # ---------------------------------------------------------------------- # Copyright (C) 2003,2004 Rafal Mantiuk and Grzegorz Krawczyk # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # ---------------------------------------------------------------------- # # @author Rafal Mantiuk, # # $Id: pfsoctavelum,v 1.2 2008/05/06 18:01:32 rafm Exp $ # # See man page for more information pin = pfsopen( "stdin" ); #fprintf( stderr, "l = %d\n", length( argv ) ); if( length( argv ) != 1 ) error( "Expecting exactly one parameter with octave code to be execuded" ); endif command = argv(){1}; ## Add missing ';' if( command(length(command)) != ";" ) command = [ command ";" ]; endif firstFrame = true; while( true ) pin = pfsget( pin ); if( pin.EOF == true ) # Are there any more frames break; endif if( firstFrame ) ## The dimensions are know only after loading the first frame pout = pfsopen( "stdout", pin.rows, pin.columns ); firstFrame = false; endif ## Copy channels and tags from the source to destination stream pout.channels = pin.channels; pout.tags = pin.tags; pout.channelTags = pin.channelTags; [Y _x _y] = pfstransform_colorspace( "XYZ", pout.channels.X, ... pout.channels.Y, pout.channels.Z, "Yxy" ); eval( command ); [pout.channels.X pout.channels.Y pout.channels.Z] = ... pfstransform_colorspace( "Yxy", Y, _x, _y, "XYZ" ); pfsput( pout ); endwhile pfsclose( pin ); if( exist( "pout" ) != 0 ) pfsclose( pout ); endif pfstools-2.2.0/src/octave/pfscrop0000664000701400070140000000216114105165614015530 0ustar rkm38rkm38#!/bin/sh if test -z "$1" || test "$1" = "--help" || (test $# != "2" && test $# != "4"); then cat < "Cropping window pos: $CW_X $CW_Y size: $CW_W $CW_H" SCRIPTFILE=`tempfile` cat >${SCRIPTFILE} < rows( Y ) || ${CW_X} + ${CW_W}-1 > columns(Y) ) error( "Cropping window exceeds image size" ); exit( 1 ) endif X = X(${CW_Y}:${CW_Y} + ${CW_H}-1, ${CW_X}:${CW_X} + ${CW_W}-1); Y = Y(${CW_Y}:${CW_Y} + ${CW_H}-1, ${CW_X}:${CW_X} + ${CW_W}-1); Z = Z(${CW_Y}:${CW_Y} + ${CW_H}-1, ${CW_X}:${CW_X} + ${CW_W}-1); pfswrite( "stdout", "X", X, "Y", Y, "Z", Z ); EOF octave2.1 -q ${SCRIPTFILE} rm ${SCRIPTFILE} pfstools-2.2.0/src/octave/pfsview_rgb.m0000664000701400070140000000215414105165614016626 0ustar rkm38rkm38function pfsview_rgb( R, G, B, window_min, window_max ) ## Shows high-dynamic range RGB image using pfsview ## ## Usage: pfsview( R, G, B[, min, max] ) ## R, G, B - red, green and blue color channels, given as linear ## window_min - minimum luminance to show (in log10 units) ## window_max - maksimum luminance to show (in log10 units) ## response function if( !is_matrix(R) || !is_matrix(G) || !is_matrix(B) || \ any(size(R) != size(G)) || any(size(G) != size(B)) ) error( "pfsview_rgb: matrices of the equal size expected as an arguments" ); endif [X Y Z] = pfstransform_colorspace( "RGB", R, G, B, "XYZ" ); hdr_name = tmpnam(); pfswrite( hdr_name, "X", X, "Y", Y, "Z", Z ); rm = sprintf ("rm -f %s", hdr_name); if( exist( "window_min" ) && exist( "window_max" ) ) minmax_window = sprintf( "--window_min %g --window_max %g ", \ window_min, window_max ); else minmax_window = ""; endif pfsv = sprintf ("pfsview %s <%s", minmax_window, hdr_name); system (sprintf ("( %s && %s ) > /dev/null 2>&1 &", pfsv, rm)); endfunction pfstools-2.2.0/src/octave/pfsclose.help0000664000701400070140000000021514105165614016617 0ustar rkm38rkm38Close pfs stream. See also help for pfsopen function. usage: pfsclose( pfs_struct ); pfs_struct - structure returned by pfsopen or pfsget pfstools-2.2.0/src/octave/pfs_close_frames.m0000664000701400070140000000057114105165614017624 0ustar rkm38rkm38function pfs_close_frames( pfs_struct ) ## Close pfs stream opened with pfs_open_frames. Removes all temporary ## files. ## ## usage: pfs_close_frames( pfs_struct ) ## if( !isfield( pfs_struct, "fid" ) ) error( "pfs_close_frames: pfs_struct wasn't opened with pfs_open_frames\n" ); endif pfsclose( pfs_struct ); fclose( pfs_struct.fid ); endfunction pfstools-2.2.0/src/octave/pfsstat.10000664000701400070140000000271114105165614015700 0ustar rkm38rkm38.TH "pfsstat" 1 .SH NAME pfsstat \- Show frame / image statistics .SH SYNOPSIS .B pfsstat .SH DESCRIPTION This command will show a short text statistic on each image in the pfs stream. In the statistics you can find: .TP \fBFile\fR - name of the input file .TP \fBWidth, Height\fR - image dimensions .TP \fBMinimum\fR - minimum luminance of an image(*), given in linear units (relative luminance in cd/m^2) and logarithmic units .TP \fBMaximum\fR - maximum luminance of an image(*) .TP \fBAverage\fR - average luminance of an image(*) .TP \fBMean\fR - mean luminance of an image(*) .PP (*) Before min, max, average, median and dynamic range is computed, the following processing is performed on an image: 1) negative and zero values are replaced with the smallest positive value (to compute logarithms); 2) the image is low-pass filtered. The low-pass filtering removes few very dark or very bright pixels that can significantly influence estimation of the dynamic range. The percentile, instead of a low-pass filter, is sometimes used for the same purpose. However, a low-pass filter is preferred to the percentile in pfstools since, taking into account the processing that is happening in the human visual system, low-frequency band filter is more plausible. .PP Note: This command requires GNU Octave. .SH EXAMPLES .TP pfsin memorial.hdr | pfsstat .PP Show statistics for the memorial image. .SH BUGS Please report bugs and comments to Rafal Mantiuk . pfstools-2.2.0/src/octave/pfsstat0000664000701400070140000000637414105165614015552 0ustar rkm38rkm38#!/usr/bin/octave -q # # This file is a part of PFSTOOLS package. # ---------------------------------------------------------------------- # Copyright (C) 2003,2004 Rafal Mantiuk and Grzegorz Krawczyk # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # ---------------------------------------------------------------------- # # @author Rafal Mantiuk, # # $Id: pfsstat,v 1.2 2005/11/07 14:40:52 rafm Exp $ # # See man page for more information pkg load signal pin = pfsopen( "stdin" ); #fprintf( stderr, "l = %d\n", length( argv ) ); if( length( argv ) != 0 ) error( "Expecting no parameters" ); endif firstFrame = true; while( true ) pin = pfsget( pin ); if( pin.EOF == true ) # Are there any more frames break; endif ## Low pass filter to eliminate influence of high frequencies on ## the statistic kernel = gausswin( 30, 2.2 ); ##*gausswin( 30, 2.2 )'; kernel = kernel / sum( kernel(:) ); ## Separate filtering for rows and cols fy = conv2( pin.channels.Y, kernel, 'same' ); fy = conv2( fy, kernel', 'same' ); ## Remove zero and negative values - set them to smalles possitive value min_positive = min( vec( fy + (fy<=0)*1000 ) ); fy = fy .* (fy >= min_positive) + min_positive .* (fy < min_positive); ## Work in log domain lfy = log10(fy); ## Calculate statistics minimum = min( vec( lfy ) ); maximum = max( vec( lfy ) ); dynamic_range = maximum - minimum; s_average = mean( vec( lfy ) ); s_median = median( vec( lfy ) ); if( !firstFrame ) fprintf( stderr, "======================\n" ); endif if( isfield( pin.tags, "FILE_NAME" ) ) fprintf( stderr, "File: %s\n", pin.tags.FILE_NAME ); endif if( isfield( pin.tags, "LUMINANCE" ) ) fprintf( stderr, "Luminance profile: %s\n", pin.tags.LUMINANCE ); endif fprintf( stderr, "Width: %d Height: %d\n", pin.columns, pin.rows ); fprintf( stderr, "Dynamic Range: \t%g [log_10]\t 1:%g [lin]\t %g [dB]\n", ... dynamic_range, 10^dynamic_range, ... round(20*dynamic_range) ); fprintf( stderr, "Minimum: \t%g [log_10]\t %g [lin]\n", ... minimum, 10^minimum ); fprintf( stderr, "Maximum: \t%g [log_10]\t %g [lin]\n", ... maximum, 10^maximum ); fprintf( stderr, "Log average: \t%g [log_10]\t %g [lin]\n", ... s_average, 10^s_average ); fprintf( stderr, "Median: \t%g [log_10]\t %g [lin]\n", ... s_median, 10^s_median ); firstFrame = false; endwhile pfsclose( pin ); pfstools-2.2.0/src/octave/pfsoctavergb.10000664000701400070140000000152214105165614016700 0ustar rkm38rkm38.TH "pfsoctavergb" 1 .SH NAME pfsoctavergb \- Process red, green and blue channels in pfs stream using Octave .SH SYNOPSIS .B pfsoctavergb .SH DESCRIPTION Use this command to execute a GNU Octave program on red, green and blue channels of each frame in the pfs stream. \fIoctave_program\fR must be given in quotation marks ("") and must not produce any output (it can output to stderr, not to stdout). Red, green and blue color channels are available in \fIoctave_program\fR as \fBR\fR, \fBG\fR and \fBB\fR matrices. .PP Note: This command requires GNU Octave. .SH EXAMPLES .TP pfsinrgbe frames_%04d.hdr | pfsoctavergb "R = R.^2; G = G.^2; B = B.^2;" | pfsview .PP Expand the dynamic range of a sequence of frames and show the result with pfsview. .SH BUGS Please report bugs and comments to Rafal Mantiuk . pfstools-2.2.0/src/octave/pfsopen.help0000664000701400070140000000253314105165614016460 0ustar rkm38rkm38Open pfs stream for reading or writing. pfs is an interchange format for high dynamic range images (see http://pfstools.sourceforge.net). usage: pfs_struct = pfsopen( fileName ); pfs_struct = pfsopen( fileName, rows, columns ); pfs_struct = pfsopen( fileName, [ rows columns ] ); fileName - name of the file to read or write. \"stdin\" or \"stdout\" for standard input and output rows - height of images to write columns - width of images to write The first usage of pfsopen opens pfs stream for reading; the second and third for writing. Use pfsget or pfsput to read or write frames or single images. You must close pfs stream with pfsclose. The stream will not be closed when pfs_struct is deleted (for example with 'clear pfs_struct'). pfs_struct is a structure that contains the following fields: EOF - set to 1 if there are no more frames; 0 otherwise FH - file handle of the file. For internal pruposes, do not use MODE - file open mode: R - for reading, W - for writing columns, rows - dimensions of each channel in the stream channels - structure that contains channels represented as real matrices tags - structure that contains tags represented as strings channelTags - structure that contains a structure for each channel, which contains tags. The format of the latter structure is the same as for 'tags' field. pfstools-2.2.0/src/octave/help_files/0002775000701400070140000000000014105165614016245 5ustar rkm38rkm38pfstools-2.2.0/src/octave/help_files/pfsopen_help.h0000664000701400070140000000266114105165614021103 0ustar rkm38rkm38"Open pfs stream for reading or writing. pfs is an interchange format for high dynamic range images (see http://pfstools.sourceforge.net).\n" "\n" "usage: pfs_struct = pfsopen( fileName );\n" " pfs_struct = pfsopen( fileName, rows, columns );\n" " pfs_struct = pfsopen( fileName, [ rows columns ] );\n" " \n" " fileName - name of the file to read or write. \"stdin\" or \"stdout\" for standard input and output\n" " rows - height of images to write\n" " columns - width of images to write\n" "\n" "The first usage of pfsopen opens pfs stream for reading; the second and third for writing. Use pfsget or pfsput to read or write frames or single images. You must close pfs stream with pfsclose. The stream will not be closed when pfs_struct is deleted (for example with 'clear pfs_struct').\n" "\n" "pfs_struct is a structure that contains the following fields:\n" " EOF - set to 1 if there are no more frames; 0 otherwise\n" " FH - file handle of the file. For internal pruposes, do not use\n" " MODE - file open mode: R - for reading, W - for writing\n" " columns, rows - dimensions of each channel in the stream\n" " channels - structure that contains channels represented as real matrices\n" " tags - structure that contains tags represented as strings\n" " channelTags - structure that contains a structure for each channel, which contains tags. The format of the latter structure is the same as for 'tags' field.\n" " \n" ; pfstools-2.2.0/src/octave/help_files/pfsread_help.h0000664000701400070140000000111314105165614021044 0ustar rkm38rkm38"DEPRECIATED! Use pfsopen/pfsget/pfsclose instead\n" "Read selected PFS channels from stdin.\n" "\n" "usage: [C1 C2 ...] = pfsread( fileName, c1n, c2n, ...);\n" " fileName - name of the file to read, or \"stdin\" for standard input\n" " c1n - name of the first channel\n" " c2n - name of the second channel\n" " C1 - matrix with the first channel\n" " C2 - matrix with the second channel\n" "\n" "Channel names are case sensitive\n" "\n" "Returns \"EOF\" strings instead of matrices if there are no more frames. Note the function can read multiple frames only from the stdin" ; pfstools-2.2.0/src/octave/help_files/pfstransform_colorspace_help.h0000664000701400070140000000073514105165614024367 0ustar rkm38rkm38"Tranform between color spaces using pfs library.\n" "\n" "usage: [C1 C2 C2] = pfstranform_colorspace( inCSname, c1, c2, c3, outCSname );\n" " inCSname - name of the input color space\n" " c - matrix with n-th channel of input color space\n" " C - matrix with n-th channel of output color space\n" " outCSname - name of the output color space\n" "\n" "Recognized color space names: 'XYZ', 'RGB', 'sRGB', 'YUV', 'Yxy'.\n" "Color space names are case insensitive.\n" ; pfstools-2.2.0/src/octave/help_files/pfsget_help.h0000664000701400070140000000042714105165614020717 0ustar rkm38rkm38"Read next frame from the pfs stream. See also help for pfsopen function.\n" "\n" "usage: new_pfs_struct = pfsget( pfs_struct );\n" " pfs_struct - the structure returned by pfsopen or pfsget\n" " new_pfs_struct - new structure with new fields containing channels and tags\n" ; pfstools-2.2.0/src/octave/help_files/pfsput_help.h0000664000701400070140000000106214105165614020744 0ustar rkm38rkm38"Write frame to the pfs stream. See also help for pfsopen function.\n" "\n" "usage: pfsput( pfs_struct );\n" " pfs_struct - the structure returned by pfsopen. The stream must be open for writing\n" "\n" "Typical usage:\n" "\n" "## Create you image \n" "Y = ones( 512, 512 );\n" "Y(256,:) = 10;\n" "\n" "## Create pfs stream for writing\n" "pout = pfsopen( \"output.pfs\", size( Y ) );\n" "\n" "## Add channels\n" "pout.channels.Y = Y;\n" "\n" "## Write frame. You can 'put' multiple frames.\n" "pfsput( pout );\n" "\n" "##Close stream\n" "pfsclose( pout )\n" ; pfstools-2.2.0/src/octave/help_files/pfsclose_help.h0000664000701400070140000000023714105165614021244 0ustar rkm38rkm38"Close pfs stream. See also help for pfsopen function.\n" "\n" "usage: pfsclose( pfs_struct );\n" "\n pfs_struct - structure returned by pfsopen or pfsget" ; pfstools-2.2.0/src/octave/help_files/pfswrite_help.h0000664000701400070140000000070714105165614021273 0ustar rkm38rkm38"DEPRECIATED! Use pfsopen/pfsput/pfsclose instead\n" "Write matrices as channels in PFS format to stdout.\n" "\n" "usage: pfswrite( fileName, c1n, C1, c2n, C2, ...);\n" " fileName - name of the file to write, or \"stdout\" for standard output\n" " c1n - name of the first channel\n" " C1 - matrix with the first channel\n" " c2n - name of the second channel\n" " C2 - matrix with the second channel\n" "\n" "Channel names are case sensitive\n" ; pfstools-2.2.0/src/octave/pfs_open_frames.m0000664000701400070140000000265414105165614017464 0ustar rkm38rkm38function pfs_struct = pfs_open_frames( filePattern, frameSize ) ## Open frames / image in one of the suppoted formats for reading or ## writing. Reading or writing frames is done with pfsput or pfsget. ## ## usage: pfs_struct = pfs_open_frames( filePattern, size ) ## pfs_struct = pfs_open_frames( filePattern ) ## ## This command is equivalent to pfsopen, but can handle files in any ## format supported by pfstools. 'filePattern' can specify files, ## frames, including '--frames' and '--skip-missing', similarly as ## 'pfsin' / 'pfsout' commands. You can pass also additional options, ## like --compression=RLE for exr files in filePattern. ## All option that take an argument (except --frames) must given in ## the form --option=value, that is without a space between an ## option and its argument. ## ## pfs_close_frames should be used instead of pfs_close to close pfsstream. ## doWrite = exist( "frameSize" ); try if( doWrite ) execStr = sprintf( "pfsout %s", filePattern ); fid = popen( execStr, "w" ); pfs_struct = pfsopen( fid, frameSize ); pfs_struct.fid = fid; else execStr = sprintf( "pfsin %s", filePattern ); fid = popen( execStr, "r" ); pfs_struct = pfsopen( fid ); pfs_struct.fid = fid; endif catch fclose( pfs_struct.fid ); error( [ "pfs_open_frames: " __error_text__ ] ); end_try_catch endfunction pfstools-2.2.0/src/octave/pfsread.help0000664000701400070140000000102714105165614016427 0ustar rkm38rkm38DEPRECIATED! Use pfsopen/pfsget/pfsclose instead Read selected PFS channels from stdin. usage: [C1 C2 ...] = pfsread( fileName, c1n, c2n, ...); fileName - name of the file to read, or \"stdin\" for standard input c1n - name of the first channel c2n - name of the second channel C1 - matrix with the first channel C2 - matrix with the second channel Channel names are case sensitive Returns \"EOF\" strings instead of matrices if there are no more frames. Note the function can read multiple frames only from the stdin pfstools-2.2.0/src/octave/pfs_write_rgb.m0000664000701400070140000000133114105165614017141 0ustar rkm38rkm38function pfs_write_rgb( fileName, R, G, B ) ## Write an hdr image file (formats accepted by pfsout). You can ## specify additional options in fileName, such as ## "--compression=PXR24" for OpenEXR files. Check manual pages of ## pfsout* commands for the list of available options. ## ## pfs_write_rgb( fileName, R, G, B ) unwind_protect fid = popen( sprintf( "pfsout %s", fileName ), "w" ); pfs = pfsopen( fid, size( R ) ); pfs.channels = struct; [pfs.channels.X pfs.channels.Y pfs.channels.Z] = ... pfstransform_colorspace( "RGB", R, G, B, "XYZ" ); pfsput( pfs ); unwind_protect_cleanup pfsclose( pfs ); fclose( fid ); end_unwind_protect endfunction pfstools-2.2.0/src/octave/CMakeLists.txt0000664000701400070140000000367114105165614016700 0ustar rkm38rkm38include_directories("${PROJECT_BINARY_DIR}/" "${PROJECT_SOURCE_DIR}/src/pfs" "${PROJECT_SOURCE_DIR}/src/octave/help_files" ${OCTAVE_DEST_DIR}) link_directories("${PROJECT_SOURCE_DIR}/src/pfs") set(HELP_FILES pfsget.help pfsopen.help pfsput.help pfsread.help pfstransform_colorspace.help pfswrite.help) set(SRC_OCT pfsread pfswrite pfstransform_colorspace pfsopen pfsget pfsput pfsclose) set(SRC_M pfsview.m pfsview_rgb.m pfsview_list.m pfs_read_rgb.m pfs_read_xyz.m pfs_read_luminance.m pfs_write_rgb.m pfssize.m pfs_open_frames.m pfs_close_frames.m) set(OCT_SCRIPTS pfsoctavelum pfsoctavergb pfsstat) set(CMAKE_CXXFLAGS "-cxx -largeArrayDims") set(ENV{CXXFLAGS} ${CMAKE_CXXFLAGS}) SET( CMAKE_CXX_COMPILER MKOCTFILE ) SET( CMAKE_C_COMPILER MKOCTFILE ) SET( CMAKE_SHARED_LIBRARY_PREFIX ) set(HEADERS "${PROJECT_SOURCE_DIR}/src/pfs/") #Building phase foreach(SRC ${SRC_OCT}) get_filename_component(OCT_NAME ${SRC} NAME) add_custom_command( OUTPUT ${OCT_NAME}.oct COMMAND ${MKOCTFILE} "${CMAKE_CURRENT_SOURCE_DIR}/${OCT_NAME}.cpp" ARGS -I${HEADERS} -I${CMAKE_CURRENT_SOURCE_DIR}/help_files -L${PROJECT_BINARY_DIR}/src/pfs -lpfs DEPENDS ${OCT_NAME}.cpp COMMENT "Generating ${OCT_NAME}.oct" VERBATIM) endforeach(SRC) foreach(SRC ${SRC_OCT}) add_custom_target(${SRC}.mkoct ALL DEPENDS ${SRC}.oct) endforeach(SRC) #Installation foreach(SRC ${SRC_OCT}) install (FILES ${CMAKE_CURRENT_BINARY_DIR}/${SRC}.oct PERMISSIONS OWNER_EXECUTE GROUP_EXECUTE WORLD_EXECUTE OWNER_WRITE WORLD_READ GROUP_READ OWNER_READ DESTINATION "${OCTAVE_OCT_DIR}") endforeach(SRC) foreach(SRC ${SRC_M}) install (FILES ${SRC} DESTINATION "${OCTAVE_M_DIR}") endforeach(SRC) install (FILES ${OCT_SCRIPTS} PERMISSIONS OWNER_EXECUTE GROUP_EXECUTE WORLD_EXECUTE OWNER_WRITE WORLD_READ GROUP_READ OWNER_READ DESTINATION bin) foreach(SCRIPT ${OCT_SCRIPTS}) install (FILES ${SCRIPT}.1 DESTINATION ${MAN_DIR}) endforeach(SCRIPT ${OCT_SCRIPTS}) pfstools-2.2.0/src/octave/pfsview_list.m0000664000701400070140000000121514105165614017024 0ustar rkm38rkm38function pfsview_list( M, idx ) ## Shows list of high-dynamic range matrices using pfsview ## ## Usage: pfsview_list( list, idx ) ## list - list of channels ## idx - vector of list indexes to show (optional) if !exist("idx") idx = 1:length(M); endif execStr = "pfswrite( hdr_name"; for i=idx chStr = sprintf( ", \"ch%d\",real(nth(M,%d))", i, i ); execStr = [ execStr chStr ]; endfor execStr = strcat( execStr, " );" ); hdr_name = tmpnam(); eval( execStr ); xv = sprintf ("pfsview <%s", hdr_name); rm = sprintf ("rm -f %s", hdr_name); system (sprintf ("( %s && %s ) > /dev/null 2>&1 &", xv, rm)); endfunction pfstools-2.2.0/src/octave/pfswrite.cpp0000664000701400070140000000704714105165614016510 0ustar rkm38rkm38/** * @brief GNU Octave wrapper - write matrices as channels to the PFS stream * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Rafal Mantiuk and Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Rafal Mantiuk, * * $Id: pfswrite.cpp,v 1.1 2005/06/15 13:36:54 rafm Exp $ */ #include #include #include #include #define SCRIPT_NAME "pfswrite" static const char *helpString = #include "pfswrite_help.h" DEFUN_DLD( pfswrite, args, , helpString) { octave_value_list retval; int nargin = args.length(); if( nargin < 3 ) { error( SCRIPT_NAME ": Improper usage!"); return retval; } // Check params if( !args(0).is_string() ) { error( SCRIPT_NAME ": expected file name as the first argument!"); return retval; } for( int i = 1; i < nargin; i += 2 ) if( (i+1) >= nargin || !args(i).is_string() || !args(i+1).is_real_matrix() ) { error( SCRIPT_NAME ": expected (channelName, matrix) pair argument!"); return retval; } FILE *fh; const char *fileName = args(0).string_value().c_str(); if( !strcasecmp( "stdout", fileName ) ) fh = stdout; else { fh = fopen( fileName, "ab" ); if( fh == NULL ) { error( SCRIPT_NAME ": cannot open file for writing!"); return retval; } } try { pfs::DOMIO ctx; pfs::Frame *frame = NULL; int rows, cols; for( int i = 1; i < nargin; i += 2 ) { Matrix mat(args(i+1).matrix_value()); if( frame == NULL ) { // Fist channel frame = ctx.createFrame(mat.cols(), mat.rows()); rows = mat.rows(); cols = mat.cols(); } else if( mat.rows() != rows || mat.cols() != cols ) { // If size of this and the first channel differ error( SCRIPT_NAME ": all matrices must be of the same size"); continue; } const char *channelName = args(i).string_value().c_str(); pfs::Channel *channel = frame->createChannel( channelName ); int index = 0; for( int r = 0; r < channel->getRows(); r++ ) // Copy octave matrix to channel for( int c = 0; c < channel->getCols(); c++ ) { (*channel)(index++) = mat(r,c); } } ctx.writeFrame( frame, fh ); ctx.freeFrame( frame ); } catch( pfs::Exception ex ) { error( "%s: %s", SCRIPT_NAME, ex.getMessage() ); } if( fh != stdout ) fclose( fh ); return retval; } pfstools-2.2.0/src/octave/pfsget.help0000664000701400070140000000040114105165614016266 0ustar rkm38rkm38Read next frame from the pfs stream. See also help for pfsopen function. usage: new_pfs_struct = pfsget( pfs_struct ); pfs_struct - the structure returned by pfsopen or pfsget new_pfs_struct - new structure with new fields containing channels and tags pfstools-2.2.0/src/octave/pfsread.cpp0000664000701400070140000000656114105165614016271 0ustar rkm38rkm38/** * @brief GNU Octave wrapper - read selected channels from the PFS stream * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Rafal Mantiuk and Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Rafal Mantiuk, * * $Id: pfsread.cpp,v 1.1 2005/06/15 13:36:54 rafm Exp $ */ #include #include //#include // conflicts with config.h from octave distribution #include #include #define SCRIPT_NAME "pfsread" static const char *helpString = #include "pfsread_help.h" DEFUN_DLD( pfsread, args, , helpString) { octave_value_list retval; int nargin = args.length(); if( nargin < 1 ) { error( SCRIPT_NAME ": Improper usage!"); return retval; } if( !args(0).is_string() ) { error( SCRIPT_NAME ": expected file name as the first argument!"); return retval; } FILE *fh; const char *fileName = args(0).string_value().c_str(); if( !strcasecmp( "stdin", fileName ) ) fh = stdin; else { fh = fopen( fileName, "rb" ); if( fh == NULL ) { error( SCRIPT_NAME ": cannot open file for reading!"); return retval; } } try { pfs::DOMIO ctx; pfs::Frame *frame = ctx.readFrame( fh ); if( frame == NULL ) { // No more frames for( int i = 0; i < nargin; i++ ) retval(i) = std::string( "EOF" ); return retval; } for( int i = 1; i < nargin; i++ ) { if( !args(i).is_string() ) { error( SCRIPT_NAME ": expected string argument!"); break; } const char *channelName = args(i).string_value().c_str(); pfs::Channel *channel = frame->getChannel( channelName ); if( channel == NULL ) { error( SCRIPT_NAME ": channel not found!"); break; } Matrix mat( channel->getRows(), channel->getCols() ); int index = 0; for( int r = 0; r < channel->getRows(); r++ ) // Copy channel data to Octave matrix for( int c = 0; c < channel->getCols(); c++ ) { mat(r,c) = (*channel)(index++); } retval(i-1) = mat; } ctx.freeFrame( frame ); } catch( pfs::Exception ex ) { error( "%s: %s", SCRIPT_NAME, ex.getMessage() ); } if( fh != stdin ) fclose( fh ); return retval; } pfstools-2.2.0/src/octave/pfsoctavergb0000664000701400070140000000435014105165614016543 0ustar rkm38rkm38#!/usr/bin/octave -q # # This file is a part of PFSTOOLS package. # ---------------------------------------------------------------------- # Copyright (C) 2003,2004 Rafal Mantiuk and Grzegorz Krawczyk # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # ---------------------------------------------------------------------- # # @author Rafal Mantiuk, # # $Id: pfsoctavergb,v 1.2 2008/05/06 18:01:32 rafm Exp $ # # See man page for more information pin = pfsopen( "stdin" ); #fprintf( stderr, "l = %d\n", length( argv ) ); if( length( argv ) != 1 ) error( "Expecting exactly one parameter with octave code to be execuded" ); endif command = argv(){1}; ## Add missing ';' if( command(length(command)) != ";" ) command = [ command ";" ]; endif firstFrame = true; while( true ) pin = pfsget( pin ); if( pin.EOF == true ) # Are there any more frames break; endif if( firstFrame ) ## The dimensions are know only after loading the first frame pout = pfsopen( "stdout", pin.rows, pin.columns ); firstFrame = false; endif ## Copy channels and tags from the source to destination stream pout.channels = pin.channels; pout.tags = pin.tags; pout.channelTags = pin.channelTags; [R G B] = pfstransform_colorspace( "XYZ", pout.channels.X, ... pout.channels.Y, pout.channels.Z, "RGB" ); eval( command ); [pout.channels.X pout.channels.Y pout.channels.Z] = ... pfstransform_colorspace( "RGB", R, G, B, "XYZ" ); pfsput( pout ); endwhile pfsclose( pin ); if( exist( "pout" ) != 0 ) pfsclose( pout ); endif pfstools-2.2.0/src/octave/pfssize.m0000664000701400070140000000363114105165614015775 0ustar rkm38rkm38function varargout = pfssize( n_rows, n_cols, varargin ) ## Resizes the set of matrices using pfssize. All matrices must be ## of the same size. ## ## Usage: [rchannel [,rchannel...]] = pfssize( rows, cols, [filter], channel [,channel...] ) ## n_rows - number of rows of the destination matrix ## n_cols - number of columns of the destination matrix ## filter - a string with the name of the filter to use. Recognized ## names: BOX, LINEAR, MITCHEL ## channel - a real matrix ## rchannel - resized matrix if( !isscalar( n_rows ) || !isscalar( n_cols ) ) error( "pfssize: bad arguments" ); return endif execStr = "pfswrite( hdr_in_name"; filter_type = "LINEAR"; channel_num = 1; for i=1:length( varargin ) if( ischar( varargin{i} ) ) filter_type = varargin{i}; continue; elseif( !ismatrix(varargin{i}) || !isreal(varargin{i}) ) error( "pfssize: each channel must be a matrix of real-valued numbers" ); return endif chStr = sprintf( ", \"ch%d\", varargin{%d}", channel_num++, i ); execStr = [ execStr chStr ]; endfor execStr = strcat( execStr, " );" ); hdr_in_name = tmpnam(); hdr_out_name = tmpnam(); eval( execStr ); cmd = sprintf ("pfssize -x %d -y %d --filter %s <%s >%s", \ n_cols, n_rows, filter_type, hdr_in_name, hdr_out_name ); system( cmd ); execStr = "pfsread( hdr_out_name"; retStr = "["; channel_num = 1; for i=1:length( varargin ) if( ischar( varargin{i} ) ) continue; endif if( channel_num!=1 ) separator = ", "; else separator = ""; endif retStr = [ retStr separator sprintf( "varargout{%d} ", channel_num ) ]; execStr = [ execStr sprintf( ", \"ch%d\"", channel_num++ ) ]; endfor retStr = [ retStr "] = " ]; execStr = [ retStr execStr " );" ]; eval( execStr ); unlink( hdr_in_name ); unlink( hdr_out_name ); endfunction pfstools-2.2.0/src/octave/pfssaturate0000664000701400070140000000433414105165614016421 0ustar rkm38rkm38#!/usr/bin/octave -q # # This file is a part of PFSTOOLS package. # ---------------------------------------------------------------------- # Copyright (C) 2003,2004 Rafal Mantiuk and Grzegorz Krawczyk # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # ---------------------------------------------------------------------- # # @author Rafal Mantiuk, # # $Id: pfssaturate,v 1.1 2005/11/02 16:48:47 rafm Exp $ # # See man page for more information pin = pfsopen( "stdin" ); #fprintf( stderr, "l = %d\n", length( argv ) ); if( length( argv ) != 1 ) error( "Expecting a single parameter with saturation ratio" ); endif s = to_double(argv{1}); firstFrame = true; while( true ) pin = pfsget( pin ); if( pin.EOF == true ) # Are there any more frames break; endif if( firstFrame ) ## The dimensions are know only after loading the first frame pout = pfsopen( "stdout", pin.rows, pin.columns ); firstFrame = false; endif ## Copy channels and tags from the source to destination stream pout.channels = pin.channels; pout.tags = pin.tags; pout.channelTags = pin.channelTags; [Y u v] = pfstransform_colorspace( "XYZ", pout.channels.X, \ pout.channels.Y, pout.channels.Z, "Yuv" ); white.u = 0.19784; white.v = 0.46832; u = (u - white.u)*s + white.u; v = (v - white.v)*s + white.v; [pout.channels.X pout.channels.Y pout.channels.Z] = \ pfstransform_colorspace( "Yuv", Y, u, v, "XYZ" ); pfsput( pout ); endwhile pfsclose( pin ); if( exist( "pout" ) != 0 ) pfsclose( pout ); endif pfstools-2.2.0/src/octave/octave_util.h0000664000701400070140000000332314105165614016621 0ustar rkm38rkm38#ifndef OCTAVE_UTIL_H #define OCTAVE_UTIL_H /** * @brief Utility functions for interfacing pfs::Array2D with Octave * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Rafal Mantiuk and Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Rafal Mantiuk, * * $Id: octave_util.h,v 1.1 2005/06/15 13:36:54 rafm Exp $ */ #include #include inline void copy( const Matrix &in, pfs::Array2D *out ) { int index = 0; for( int r = 0; r < in.rows(); r++ ) for( int c = 0; c < in.cols(); c++ ) { (*out)(index++) = (float)in(r,c); } } inline void copy( const pfs::Array2D *in, Matrix &out ) { int index = 0; for( int r = 0; r < in->getRows(); r++ ) for( int c = 0; c < in->getCols(); c++ ) { out(r,c) = (double)(*in)(index++); } } #endif pfstools-2.2.0/src/octave/pfstransform_colorspace.cpp0000664000701400070140000000650614105165614021602 0ustar rkm38rkm38/** * @brief GNU Octave wrapper - tranform between color spaces using pfs library * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Rafal Mantiuk and Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Rafal Mantiuk, * * $Id: pfstransform_colorspace.cpp,v 1.1 2005/06/15 13:36:54 rafm Exp $ */ #include #include #include #include #include "octave_util.h" static pfs::ColorSpace findColorSpace( const char *name ); #define SCRIPT_NAME "pfstransform_colorspace" static const char *helpString = #include "pfstransform_colorspace_help.h" DEFUN_DLD( pfstransform_colorspace, args, , helpString) { octave_value_list retval; int nargin = args.length(); if( nargin != 5 || !args(0).is_string() || !args(1).is_real_matrix() || !args(2).is_real_matrix() || !args(3).is_real_matrix() || !args(4).is_string() ) { error( SCRIPT_NAME ": Improper usage!"); return retval; } try { pfs::ColorSpace inCS = findColorSpace( args(0).string_value().c_str() ); pfs::ColorSpace outCS = findColorSpace( args(4).string_value().c_str() ); const int rows = args(1).matrix_value().rows(), cols = args(1).matrix_value().cols(); pfs::Array2DImpl c1Buf( cols, rows ), c2Buf( cols, rows ), c3Buf( cols, rows ); copy( args(1).matrix_value(), &c1Buf ); copy( args(2).matrix_value(), &c2Buf ); copy( args(3).matrix_value(), &c3Buf ); pfs::transformColorSpace( inCS, &c1Buf, &c2Buf, &c3Buf, outCS, &c1Buf, &c2Buf, &c3Buf ); Matrix c1 = Matrix( rows, cols ); Matrix c2 = Matrix( rows, cols ); Matrix c3 = Matrix( rows, cols ); copy( &c1Buf, c1 ); copy( &c2Buf, c2 ); copy( &c3Buf, c3 ); retval(0) = c1; retval(1) = c2; retval(2) = c3; } catch( pfs::Exception ex ) { error( "%s: %s", SCRIPT_NAME, ex.getMessage() ); } return retval; } static pfs::ColorSpace findColorSpace( const char *name ) { if( !strcasecmp( name, "XYZ" ) ) return pfs::CS_XYZ; else if( !strcasecmp( name, "RGB" ) ) return pfs::CS_RGB; else if( !strcasecmp( name, "SRGB" ) ) return pfs::CS_SRGB; else if( !strcasecmp( name, "YUV" ) ) return pfs::CS_YUV; else if( !strcasecmp( name, "YXY" ) ) return pfs::CS_Yxy; throw pfs::Exception( "Not recognized color space" ); } pfstools-2.2.0/src/octave/pfsoctavelum.10000664000701400070140000000153114105165614016723 0ustar rkm38rkm38.TH "pfsoctavelum" 1 .SH NAME pfsoctavelum \- Process luminance in pfs stream using Octave .SH SYNOPSIS .B pfsoctavelum .SH DESCRIPTION Use this command to execute a GNU Octave program on luminance channel of each frame in the pfs stream. \fIoctave_program\fR must be given in quotation marks ("") and must not produce any output (it can output to stderr, not to stdout). Color channels are scaled proportionaly to the luminance channel. Luminance channel is represented by matrix \fIY\fR. .PP Note: This command requires GNU Octave. .SH EXAMPLES .TP pfsinrgbe frames_%04d.hdr | pfsoctavelum "Y = conv2( Y, gaussian( 10, 1/5 )*gaussian( 10, 1/5 )', 'same' );" | pfsview .PP Blur the sequence of frames with gaussian blur and show the result with pfsview. .SH BUGS Please report bugs and comments to Rafal Mantiuk . pfstools-2.2.0/src/octave/pfsput.help0000664000701400070140000000073114105165614016325 0ustar rkm38rkm38Write frame to the pfs stream. See also help for pfsopen function. usage: pfsput( pfs_struct ); pfs_struct - the structure returned by pfsopen. The stream must be open for writing Typical usage: ## Create you image Y = ones( 512, 512 ); Y(256,:) = 10; ## Create pfs stream for writing pout = pfsopen( \"output.pfs\", size( Y ) ); ## Add channels pout.channels.Y = Y; ## Write frame. You can 'put' multiple frames. pfsput( pout ); ##Close stream pfsclose( pout ) pfstools-2.2.0/src/octave/pfsview.m0000664000701400070140000000407614105165614016001 0ustar rkm38rkm38function pfsview( varargin ) ## Shows set of high-dynamic range matrices using pfsview ## ## Usage: pfsview( ["title",] channel [,"channel name"], ... ) ## ## title - (optional) a title to be displayed in the title bar of pfsview ## channel - a real matrix, or a cell array of real matrixes ## channel name - (optional) name for the channel given as next argument C = varargin{1}; width = -1; height = -1; title = "Octave"; n = 1; for i=1:length( varargin ) if iscell(varargin{i}) C = varargin{i}; width = columns( C{1} ); height = rows( C{1} ); for j=1:length(C) ch_name = sprintf( "ch%d", n++ ); if( !exist( "channels" ) ) channels = struct( ch_name, C{j} ); else channels = setfield( channels, ch_name, C{j} ); endif endfor elseif( ismatrix( varargin{i} ) ) ch_name = sprintf( "ch%d", n++ ); if( !exist( "channels" ) ) channels = struct( ch_name, varargin{i} ); else channels = setfield( channels, ch_name, varargin{i} ); endif width = columns( varargin{i} ); height = rows( varargin{i} ); elseif( ischar( varargin{i} ) ) if( i == 1 ) title = varargin{i}; else if( !exist( "channels" ) || !exist( "ch_name" ) ) error( "channel_name argument must follow channel argument" ); else channels = setfield( channels, varargin{i}, ... getfield( channels, ch_name ) ); channels = rmfield( channels, ch_name ); clear ch_name; endif endif endif endfor hdr_name = tmpnam(); pfsout = pfsopen( hdr_name, height, width ); pfsout.channels = channels; pfsout.tags.FILE_NAME = title; ## This should go after 'system' call, but it is so fifo seems to be ## not initialized for reading pfsput( pfsout ); xv = sprintf ("pfsview <%s", hdr_name); rm = sprintf ("rm -f %s", hdr_name); system (sprintf ("( %s && %s ) > /dev/null 2>&1 &", xv, rm)); pfsclose( pfsout ); endfunction pfstools-2.2.0/src/octave/pfsclose.cpp0000664000701400070140000000444414105165614016461 0ustar rkm38rkm38/** * @brief Close pfs stream * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Rafal Mantiuk and Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Rafal Mantiuk, * * $Id: pfsclose.cpp,v 1.1 2005/06/15 13:36:54 rafm Exp $ */ #include #include #include //#include "../../config.h" // conflicts with config.h from octave distribution #include #include #if OCTAVE_MAJOR_VERSION < 4 || (OCTAVE_MAJOR_VERSION == 4 && OCTAVE_MINOR_VERSION < 4) #define isstruct is_map #endif #define SCRIPT_NAME "pfsclose" static const char *helpString = #include "pfsclose_help.h" DEFUN_DLD( pfsclose, args, , helpString ) { octave_value_list retval; int nargin = args.length(); // Get arguments and check if they are legal if( nargin != 1 || !args(0).isstruct() ) { error( SCRIPT_NAME ": Improper usage!"); return retval; } octave_map pfsStream = args(0).map_value(); octave_scalar_map::const_iterator itFH = pfsStream.seek( "FH" ); if( itFH == pfsStream.end() || !pfsStream.contents( itFH )(0).is_real_scalar() ) { error( SCRIPT_NAME ": FH field missing in the structure or it has wrong type"); return retval; } FILE *fh = (FILE*)((long)(pfsStream.contents( itFH )(0).double_value())); // close file if not stdin or stdout if( fh != stdin && fh != stdout ) fclose( fh ); return retval; } pfstools-2.2.0/src/octave/pfsget.cpp0000664000701400070140000001220014105165614016120 0ustar rkm38rkm38/** * @brief Read frame from pfs stream in GNU Octave * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Rafal Mantiuk and Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Rafal Mantiuk, * * $Id: pfsget.cpp,v 1.1 2005/06/15 13:36:54 rafm Exp $ */ #include #include #include #include //#include "../../config.h" // conflicts with config.h from octave distribution #include #include #if OCTAVE_MAJOR_VERSION < 4 || (OCTAVE_MAJOR_VERSION == 4 && OCTAVE_MINOR_VERSION < 4) #define isstruct is_map #endif #define SCRIPT_NAME "pfsget" static const char *helpString = #include "pfsget_help.h" DEFUN_DLD( pfsget, args, , helpString) { octave_value_list retval; int nargin = args.length(); if( nargin != 1 || !args(0).isstruct() ) { error( SCRIPT_NAME ": Improper usage!"); return retval; } octave_map pfsStream = args(0).map_value(); octave_map::const_iterator itFH = pfsStream.seek( "FH" ); if( itFH == pfsStream.end() || !pfsStream.contents( itFH )(0).is_real_scalar() ) { error( SCRIPT_NAME ": FH field missing in the structure or it has wrong type"); return retval; } FILE *fh = (FILE*)((long)(pfsStream.contents( itFH )(0).double_value())); octave_map::const_iterator itMode = pfsStream.seek( "MODE" ); if( itMode == pfsStream.end() || !pfsStream.contents( itMode )(0).is_string() ) { error( SCRIPT_NAME ": MODE field missing in the structure or it has wrong type"); return retval; } if( pfsStream.contents( itMode )(0).string_value() != "R" ) { error( SCRIPT_NAME ": Can not read from stream open for writing." ); return retval; } pfsStream.del( "channels" ); pfsStream.del( "tags" ); pfsStream.del( "channelTags" ); try { pfs::DOMIO ctx; pfs::Frame *frame = ctx.readFrame( fh ); if( frame == NULL ) { // No more frames pfsStream.assign( "EOF", octave_value(true) ); } else { // Not EOF // Set width and height { pfsStream.assign( "columns", octave_value(frame->getWidth()) ); pfsStream.assign( "rows", octave_value(frame->getHeight()) ); } // Add channels as matrices to pfs stream struct { octave_scalar_map channels; pfs::ChannelIteratorPtr cit( frame->getChannelIterator() ); while( cit->hasNext() ) { pfs::Channel *ch = cit->getNext(); Matrix mat( ch->getRows(), ch->getCols() ); int index = 0; for( int r = 0; r < ch->getRows(); r++ ) // Copy channel data to Octave matrix for( int c = 0; c < ch->getCols(); c++ ) { mat(r,c) = (*ch)(index++); } channels.assign( ch->getName(), mat ); } pfsStream.assign( "channels", octave_value(channels) ); } //Add tags { octave_scalar_map tags; pfs::TagIteratorPtr it( frame->getTags()->getIterator() ); while( it->hasNext() ) { const char *tagName = it->getNext(); tags.assign( tagName, octave_value( frame->getTags()->getString( tagName )) ); } pfsStream.assign( "tags", octave_value(tags) ); octave_scalar_map channelTagList; //Copy all channel tags pfs::ChannelIteratorPtr cit( frame->getChannelIterator() ); while( cit->hasNext() ) { pfs::Channel *ch = cit->getNext(); octave_scalar_map channelTags; pfs::TagIteratorPtr tit( ch->getTags()->getIterator() ); while( tit->hasNext() ) { const char *tagName = tit->getNext(); channelTags.assign( tagName, octave_value(ch->getTags()->getString( tagName )) ); } channelTagList.assign( ch->getName(), octave_value(channelTags) ); } pfsStream.assign( "channelTags", octave_value(channelTagList) ); } } ctx.freeFrame( frame ); } catch( pfs::Exception ex ) { error( "%s: %s", SCRIPT_NAME, ex.getMessage() ); } // if( fh != stdin ) fclose( fh ); retval.append(pfsStream); return retval; } pfstools-2.2.0/src/octave/pfsput.cpp0000664000701400070140000001631414105165614016163 0ustar rkm38rkm38/** * @brief Write frame to pfs stream in GNU Octave * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Rafal Mantiuk and Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Rafal Mantiuk, * * $Id: pfsput.cpp,v 1.2 2006/03/22 10:03:09 rafm Exp $ */ #include #include #include #include //#include "../../config.h" // conflicts with config.h from octave distribution #include #include #if OCTAVE_MAJOR_VERSION < 4 || (OCTAVE_MAJOR_VERSION == 4 && OCTAVE_MINOR_VERSION < 4) #define isstruct is_map #endif #define SCRIPT_NAME "pfsput" static const char *helpString = #include "pfsput_help.h" DEFUN_DLD( pfsput, args, , helpString) { octave_value_list retval; int nargin = args.length(); if( nargin != 1 || !args(0).isstruct() ) { error( SCRIPT_NAME ": Improper usage!"); return retval; } octave_map pfsStream = args(0).map_value(); octave_scalar_map::const_iterator itFH = pfsStream.seek( "FH" ); if( itFH == pfsStream.end() || !pfsStream.contents( itFH )(0).is_real_scalar() ) { error( SCRIPT_NAME ": FH field missing in the structure or it has wrong type"); return retval; } FILE *fh = (FILE*)((long)(pfsStream.contents( itFH )(0).double_value())); // Check mode { octave_scalar_map::const_iterator itMode = pfsStream.seek( "MODE" ); if( itMode == pfsStream.end() || !pfsStream.contents( itMode )(0).is_string() ) { error( SCRIPT_NAME ": MODE field missing in the structure or it has wrong type"); return retval; } if( pfsStream.contents( itMode )(0).string_value() != "W" ) { error( SCRIPT_NAME ": Can not write to the stream that is open for reading." ); return retval; } } // Get width & height int width, height; { octave_scalar_map::const_iterator itCols = pfsStream.seek( "columns" ); octave_scalar_map::const_iterator itRows = pfsStream.seek( "rows" ); if( itCols == pfsStream.end() || itRows == pfsStream.end() || !pfsStream.contents( itCols )(0).is_real_scalar() || !pfsStream.contents( itRows )(0).is_real_scalar() ) { error( SCRIPT_NAME ": 'rows' and 'columns' fields missing in the structure or it have wrong type"); return retval; } width = (int)pfsStream.contents( itCols )(0).double_value(); height = (int)pfsStream.contents( itRows )(0).double_value(); } // Get channels octave_map channels; { octave_scalar_map::const_iterator itChannels = pfsStream.seek( "channels" ); if( itChannels == pfsStream.end() || !pfsStream.contents( itChannels )(0).isstruct() ) { error( SCRIPT_NAME ": 'channels' field missing in the structure or it has wrong type"); return retval; } channels = pfsStream.contents( itChannels )(0).map_value(); } try { pfs::DOMIO ctx; pfs::Frame *frame = ctx.createFrame( width, height ); // For each channel in the 'channels' map for( octave_scalar_map::iterator itCh = channels.begin(); itCh != channels.end(); itCh++ ) { std::string channelName = channels.key(itCh); if( !channels.contents( itCh )(0).is_real_matrix() ) { throw pfs::Exception( "all channels must be given as real matrices" ); } Matrix channelData = channels.contents( itCh )(0).matrix_value(); if( channelData.rows() != height || channelData.columns() != width ) { throw pfs::Exception( "size of the channel must be the same as given in pfsopen" ); } pfs::Channel *pfsChannel = frame->createChannel( channelName.c_str() ); // Copy matrix to pfs::Channel int index = 0; for( int r = 0; r < pfsChannel->getRows(); r++ ) for( int c = 0; c < pfsChannel->getCols(); c++ ) { (*pfsChannel)(index++) = channelData(r,c); } } // Copy frame tags { octave_scalar_map::const_iterator itTags = pfsStream.seek( "tags" ); if( itTags != pfsStream.end() ) { if( !pfsStream.contents( itTags )(0).isstruct() ) { throw pfs::Exception( "'tags' field must be a structure" ); } octave_map tags = pfsStream.contents( itTags )(0).map_value(); for( octave_scalar_map::iterator itTag = tags.begin(); itTag != tags.end(); itTag++ ) { std::string tagName = tags.key(itTag); if( !tags.contents( itTag )(0).is_string() ) throw pfs::Exception( "all tags must be given as strings" ); std::string tagValue = tags.contents( itTag )(0).string_value(); frame->getTags()->setString( tagName.c_str(), tagValue.c_str() ); } } } // Copy channel tags { octave_scalar_map::const_iterator itChTags = pfsStream.seek( "channelTags" ); if( itChTags != pfsStream.end() ) { if( !pfsStream.contents( itChTags )(0).isstruct() ) { throw pfs::Exception( "'channelTags' field must be a structure" ); } octave_map tagChannels = pfsStream.contents( itChTags )(0).map_value(); for( octave_scalar_map::iterator itCh = tagChannels.begin(); itCh != tagChannels.end(); itCh++ ) { std::string channelName = tagChannels.key(itCh); if( !tagChannels.contents( itCh )(0).isstruct() ) { throw pfs::Exception( "each channelTags file must be a structure" ); } pfs::Channel *pfsChannel = frame->getChannel( channelName.c_str() ); if( pfsChannel == NULL ) { throw pfs::Exception( "can not set channel tag if channel is missing" ); } octave_map tags = tagChannels.contents( itCh )(0).map_value(); for( octave_scalar_map::iterator itTag = tags.begin(); itTag != tags.end(); itTag++ ) { std::string tagName = tags.key(itTag); if( !tags.contents( itTag )(0).is_string() ) throw pfs::Exception( "all channel tags must be given as strings" ); std::string tagValue = tags.contents( itTag )(0).string_value(); pfsChannel->getTags()->setString( tagName.c_str(), tagValue.c_str() ); } } } } ctx.writeFrame( frame, fh ); ctx.freeFrame( frame ); } catch( pfs::Exception ex ) { error( "%s: %s", SCRIPT_NAME, ex.getMessage() ); } return retval; } pfstools-2.2.0/src/camera/0002775000701400070140000000000014105165614014102 5ustar rkm38rkm38pfstools-2.2.0/src/camera/pfsinhdrgen.in0000664000701400070140000000431714105165614016744 0ustar rkm38rkm38#!@PERL_PATH@ -w # # This file is a part of PFS CALIBRATION package. # ---------------------------------------------------------------------- # Copyright (C) 2004 Grzegorz Krawczyk, # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # ---------------------------------------------------------------------- # # $Id: pfsinhdrgen,v 1.5 2007/08/28 15:15:24 gkrawczyk Exp $ use Getopt::Long; #--- check params GetOptions ('v|verbose' => \$verbose, 'h|help' => \$help); if( $help or @ARGV<1 ) { print STDERR "Usage: pfsinhdrgen [--verbose] \n"; exit; } ($hdrgen) = $ARGV[0]; (-e $hdrgen) or die "File does not exist '$hdrgen'"; open HDRGEN, "<$hdrgen"; #--- parse file while( $line= ) { @token = split " ", $line; $file = $token[0]; $inv_exposure_time = $token[1]; $aperture = $token[2]; $iso_speed = $token[3]; ## defaults $iso_speed = 100 if( $iso_speed==0); $aperture = 1.0 if( $aperture==0); ## APEX calculations $Av = 2 * log($aperture) / log(2); $Tv = log($inv_exposure_time) / log(2); $exposure_time = 1 / $inv_exposure_time; $Sv = log( $iso_speed / 3.125 ) / log(2); ## Brightness value $Bv = $Av+$Tv-$Sv; (-e $file) or die "Missing image '$file'"; print STDERR "pfsinhdrgen: reading $file (iso$iso_speed,f/$aperture,t=$inv_exposure_time)\n" if( $verbose );; system "pfsin $file | pfstag --set \"BV=$Bv\" --set \"ISO=$iso_speed\" --set \"aperture=$aperture\" --set \"exposure_time=$exposure_time\"\n"; } close HDRGEN; pfstools-2.2.0/src/camera/robertson02.h0000664000701400070140000000571114105165614016434 0ustar rkm38rkm38/** * @brief Robertson02 algorithm for automatic self-calibration. * * * This file is a part of PFS CALIBRATION package. * ---------------------------------------------------------------------- * Copyright (C) 2004 Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Grzegorz Krawczyk, * * $Id: robertson02.h,v 1.4 2011/02/15 15:46:27 ihrke Exp $ */ #ifndef _robertson02_h_ #define _robertson02_h_ #include struct noise_parameters { const char* camera_name; float color_coefficients[3]; float std_readout; float std_adc; }; /** * @brief Calculate camera response using Robertson02 algorithm * * @param xj [out] estimated luminance values * @param imgs reference to vector containing source exposures * @param I [out] array to put response function * @param w weights * @param M max camera output (no of discrete steps) * @return number of saturated pixels in the HDR image (0: all OK) */ int robertson02_getResponse( pfs::Array2D* output, /* output image (gray scale) */ const ExposureList &imgs, /* differrent exposures */ float *rcurve, /* response curve */ const float *weights, /* weights */ int M /* number of values in rcurve/weights */ ); /** * @brief Create HDR image by applying response curve to given images * taken with different exposures * * @param xj [out] HDR image * @param imgs reference to vector containing source exposures * @param I camera response function (array size of M) * @param w weighting function for camera output values (array size of M) * @param M number of camera output levels * @return number of saturated pixels in the HDR image (0: all OK) */ int robertson02_applyResponse( pfs::Array2D* xj, const ExposureList &imgs, const float* I, const float* w, int M ); int robertson02_applyResponseRGB( pfs::Array2D *rgb_out[], const ExposureList *imgs[], const float *resp_curve[], const float *weights, int M, const noise_parameters *camera, const float deghosting_value = -1); #endif /* #ifndef _robertson02_h_ */ pfstools-2.2.0/src/camera/pfsinme.in0000664000701400070140000000370014105165614016071 0ustar rkm38rkm38#!@BASH_PATH@ # # This file is a part of PFS CALIBRATION package. # ---------------------------------------------------------------------- # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # ---------------------------------------------------------------------- # # @author Rafal Mantiuk, # # $Id: pfsinme,v 1.6 2013/12/25 16:13:45 rafm Exp $ if test -z "$1" || test "$1" = "--help"; then cat < [...] Recognized file formats and extensions: JPEG - .jpeg, .jpg Canon 350D RAW - .cr2 (and other camera RAW formats recognized by dcraw) All listed files must be the same type, that is mising JPEG and RAW images is not allowed. You can use wildcards. See the man page for more information. EOF exit 1 fi extension=${1##*.} HDRGEN_CMD="dcraw2hdrgen" case $extension in ("jpg"|"JPG"|"jpeg"|"JPEG") HDRGEN_CMD="jpeg2hdrgen" ;; esac unamestr=`uname` if [[ "$unamestr" == 'Darwin' ]]; then hdrgen_file=`mktemp -t pfsinme` else hdrgen_file=`mktemp` fi ${HDRGEN_CMD} "$@" | sort -k 2 -n >$hdrgen_file #echo 1>&2 $hdrgen_file echo 1>&2 "pfsinme: Recognized exposures: " cat 1>&2 $hdrgen_file pfsinhdrgen $hdrgen_file rm $hdrgen_file pfstools-2.2.0/src/camera/mitsunaga99_numerical.h0000664000701400070140000000250414105165614020463 0ustar rkm38rkm38/** * @brief Algorithm for automatic HDR images capture. * * * This file is a part of PFS CALIBRATION package. * ---------------------------------------------------------------------- * Copyright (C) 2006 Radoslaw Mantiuk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Radoslaw Mantiuk, * */ #ifndef _mitsunaga99_numerical_h_ #define _mitsunaga99_numerical_h_ #define NR_LINEAR_EQUATIONS_MAX 20 class MitsunagaNumerical { public: static int linearEquationsSystem( int n, float a[][NR_LINEAR_EQUATIONS_MAX], float b[]); }; #endif pfstools-2.2.0/src/camera/responses.cpp0000664000701400070140000002116714105165614016634 0ustar rkm38rkm38/** * @brief Standard response functions * * * This file is a part of PFS CALIBRATION package. * ---------------------------------------------------------------------- * Copyright (C) 2004 Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Grzegorz Krawczyk, * Ivo Ihrke, * * $Id: responses.cpp,v 1.11 2011/02/25 13:45:14 ihrke Exp $ */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #define MIN_WEIGHT 1e-3 //#define MIN_WEIGHT 0.0f void dump_gnuplot( const char* filename, const float* array, int M ) { FILE* fp = fopen(filename, "w"); fprintf(fp, "# GNUPlot dump\n"); for( int i=0 ; i mid ) { w [ m ] = ( (float)(m) / ( HIGH_CUT * (float)M ) -0.5 )* SLOPE_LINEAR + 1.0; } if ( (float) m > HIGH_CUT * (float)M ) { float highcut = HIGH_CUT * (float)M; // fprintf ( stderr,"HighCut: %f, m: %f, sigma: %f\n", highcut, (float)m, HIGH_SIGMA ); w [ m ] = exp ( - ( (float)m - highcut) * ( (float)m- highcut) / ( 2 * HIGH_SIGMA * HIGH_SIGMA ) ) * (1.0f + SLOPE_LINEAR / 2.0f); } } } void responseLinear( float* I, int M ) { for( int m=0 ; m 0.0f ) fprintf(file, " %e %4d %e\n", log10f(I[m]), m, I[m]); else fprintf(file, " %e %4d %e\n", -6.0f, m, I[m]); fprintf(file, "\n"); } void weightsSave( FILE* file, const float* w, int M, const char* name) { // weighting function matrix header fprintf(file, "# Weighting function\n"); fprintf(file, "# data layout: weight | camera output\n"); fprintf(file, "# name: %s\n", name); fprintf(file, "# type: matrix\n"); fprintf(file, "# rows: %d\n", M); fprintf(file, "# columns: 2\n"); // save weights for( int m=0 ; mM ) std::cerr << "response: camera value out of range," << " m=" << m << std::endl; else I[m] = val; } return true; } bool weightsLoad( FILE* file, float* w, int M) { char line[1024]; int m=0,c=0; // parse weighting function matrix header while( fgets(line, 1024, file) ) if( sscanf(line, "# rows: %d\n", &m) == 1 ) break; if( m!=M ) { std::cerr << "response: number of input levels is different," << " M=" << M << " m=" << m << std::endl; return false; } while( fgets(line, 1024, file) ) if( sscanf(line, "# columns: %d\n", &c) == 1 ) break; if( c!=2 ) return false; // read response for( int i=0 ; i1 ) // there is someting to interpolate { float delta= (I[m2]-I[m1]) / (m2-m1); if( I[m2]<=0.0f ) delta = (I[m1] - I[m1-1])/2.0f; // for some wierd data use gradient for( int m=m1+1 ; m * */ #include #include #include #include #include #include #include #include #include #include "mitsunaga99_numerical.h" #include "mitsunaga99.h" #define PROG_NAME "HDRCaptureMitsunaga" extern bool verbose; /* verbose should be declared for each standalone code */ using namespace std; /** Capture HDR images based on a sequence of LD images of different exposures. * * @param imgsR, imgsG, imgsB Input image data (for R,G and B channels). * @param M Number of luma levels (256 for 8-bits image). * @param poly_degree Desired degree of inverse response function. * @param Xj, Yj, Zj Containers for output data (for R, G and B channels). * * @return HDR image data in separate Red, Green and Blue channels. */ int HDRCaptureMitsunaga::capture( ExposureList &imgsR, ExposureList &imgsG, ExposureList &imgsB, int M, int poly_degree, pfs::Array2D* Xj, pfs::Array2D* Yj, pfs::Array2D* Zj, unsigned long samples_no, float* Ir, float* Ig, float* Ib, float* weight, int comp_response ) { float co [ CH ][ 20 ]; float InvResp [ CH ][ M ]; float w [ CH ][ M ]; if ( !comp_response ) { for (int i = 0; i < M; i++) { InvResp [ R ][ i ] = Ir[ i ]; InvResp [ G ][ i ] = Ig[ i ]; InvResp [ B ][ i ] = Ib[ i ]; w[ R ][ i ] = weight[ i ]; w[ G ][ i ] = weight[ i ]; w[ B ][ i ] = weight[ i ]; } VERBOSE_STR << "Computing ... "; sort( imgsR.begin(), imgsR.end(), Exposure::msort); applyResponse( Xj, imgsR, InvResp[R], M, w[R]); sort( imgsG.begin(), imgsG.end(), Exposure::msort); applyResponse( Yj, imgsG, InvResp[G], M, w[G]); sort( imgsB.begin(), imgsB.end(), Exposure::msort); applyResponse( Zj, imgsB, InvResp[B], M, w[B]); VERBOSE_STR << "finished" << endl; } else { // generate a set of points used to compute inverse exposure randomPixels( imgsR, samples_no); // Red VERBOSE_STR << "Red channel ... "; sort( imgsR.begin(), imgsR.end(), Exposure::msort); if( getResponseFast( imgsR, poly_degree, co[R], M) > 0 ) { getInverseResponseArray( poly_degree, co[R], M, InvResp[R]); getDerivativeOfInverseResponseArray(poly_degree, co[R], M, w[R]); applyResponse( Xj, imgsR, InvResp[R], M, w[R]); } VERBOSE_STR << "computed " << co[R][0] << " " << co[R][1] << " " << co[R][2] << " " << co[R][3] << endl; // Green VERBOSE_STR << "Green channel ... "; sort( imgsG.begin(), imgsG.end(), Exposure::msort); if( getResponseFast( imgsG, poly_degree, co[G], M) > 0 ) { getInverseResponseArray( poly_degree, co[G], M, InvResp[G]); getDerivativeOfInverseResponseArray(poly_degree, co[G], M, w[G]); applyResponse( Yj, imgsG, InvResp[G], M, w[G]); } VERBOSE_STR << "computed " << co[G][0] << " " << co[G][1] << " " << co[G][2] << " " << co[G][3] << endl; // Blue VERBOSE_STR << "Blue channel ... "; sort( imgsB.begin(), imgsB.end(), Exposure::msort); if( getResponseFast( imgsB, poly_degree, co[B], M) > 0 ) { getInverseResponseArray( poly_degree, co[B], M, InvResp[B]); getDerivativeOfInverseResponseArray(poly_degree, co[B], M, w[B]); applyResponse( Zj, imgsB, InvResp[B], M, w[B]); } VERBOSE_STR << "computed " << co[B][0] << " " << co[B][1] << " " << co[B][2] << " " << co[B][3] << endl; // data for external file with the response and weights for (int i = 0; i < M; i++) { if ( InvResp [ R ][ i ] < 0 ) Ir [ i ] = 0; else Ir [ i ] = InvResp [ R ][ i ]; if ( InvResp [ G ][ i ] < 0 ) Ig [ i ] = 0; else Ig [ i ] = InvResp [ G ][ i ]; if ( InvResp [ B ][ i ] < 0 ) Ib [ i ] = 0; else Ib [ i ] = InvResp [ B ][ i ]; // TODO: there should be a separate weight for each colour channel // weight value (inverse response divided by derivative of inverse response function) weight [ i ] = (InvResp[R][i]+InvResp[G][i]+InvResp[B][i])/3.f / ( w [ R ][ i ] + w[ G ][ i ] + w[ B ][ i ] ) / 3.f; } } return 0; } /** Computes camera inverse response function based on algorithm decribed in * Tomoo Mitsunaga and Shree K. Nayar in "Radiometric Self Calibration" paper. * * @param imgs - input image data for a separate channel * @param poly_degree - desired degree of inverse response function * @param coef - array with computed polynomial cooficients * @param M - number of luma levels (256 for 8-bits data) */ int HDRCaptureMitsunaga::getResponse( const ExposureList &imgs, int poly_degree, float* coef, int M) { // number of exposures (input images) int Q = imgs.size(); if( Q < 2) { fprintf( stderr, "WARNING: not enough input images (mitsunaga_getResponse())\n"); return -1; } // frame size int P = imgs[0].yi->getCols() * imgs[0].yi->getRows(); float R; // exposure ratio int N = poly_degree; // degree of polynomial float c[N][NR_LINEAR_EQUATIONS_MAX]; for( int e = 0; e < N; e ++) for( int i = 0; i < N; i++) c[e][i] = 0; float b[N]; for( int e = 0; e < N; e ++) b[e] = 0; float M1, M2; float Imax = 1.0; for( int q = 0; q < (Q-1); q++) { // for all images - 1 R = imgs[q].ti / imgs[q+1].ti; for( int p = 0; p < P; p++) { // for all pixels M1 = (*imgs[q].yi)(p); M2 = (*imgs[q+1].yi)(p); float an = pow( M1, N) - R * pow( M2, N); for( int e = 0; e < N; e ++) { // for all equations float aj = pow( M1, e) - R * pow( M2, e); for( int i = 0; i < N; i++) { float ai = pow( M1, i) - R * pow( M2, i); c[e][i] += (( ai - an) * ( aj - an)); } b[e] += (-an * Imax * ( aj - an)); } } } MitsunagaNumerical::linearEquationsSystem( N, c, b); for( int e = 0; e < N; e ++) { if( isnan(b[e])) { fprintf(stderr, "ERROR: NaN cooefficient (mitsunaga_getResponse())\n"); return 0; } } float cn; float sum = 0; for( int e = 0; e < N; e ++) sum += b[e]; cn = 1.0 - sum; coef[0] = cn; for(int i = (N-1); i >=0; i--) { coef[N-i] = b[i]; } return N; } /** Creates radiance map based on a sequence of LDR images and a given inverse response function. * * @param xj - output array with HDR luminance data (for one color channel) * @param imgs - input image data for a separate channel * @param Iresp - array with data of inverse response function (calculated based on polynomial coefficients) * @param M - number of luma levels (256 for 8-bits data) * @param w - array with weight function data */ void HDRCaptureMitsunaga::applyResponse( pfs::Array2D* xj, ExposureList &imgs, float *rcurve, int M, float *weights) { // frame size int count = xj->getCols() * xj->getRows(); int width = xj->getCols(); int height = xj->getRows(); // number of exposures int N = imgs.size(); #if 1 float ex_sum = 0; // to normalize exposure time for( int i=0 ; i= M) m = M - 1; if( m < 0 ) { fprintf( stderr, "WARNING: wrong pixel value (mitsunaga_applyResponse())\n"); continue; } ww = weights [ m ]; // bofore the response curve handling was unified: // ww = rcurve [ m ] / weights [ m ]; // weight value (inverse response divided by derivative of inverse response function) ex = imgs [ i ].ti / ex_sum; // normalized exposure value sum += ww * rcurve [ m ] / ex; div += ww; } if( div != 0.0f ) (*xj)( j ) = sum / div; else (*xj)( j ) = 0.0f; } #endif /* this is robertson style response curve application - for testing purposes only */ #if 1 // all pixels for( int j = 0; j < width * height; j++ ) { // all exposures for each pixel float sum = 0.0f; float div = 0.0f; for( int i = 0 ; i < N ; i++ ) { int m = (int) ((*imgs[ i ].yi)( j ) * (float)M); float ti = imgs[ i ].ti; float w = weights[m]; sum += w / ti * rcurve [ m ]; div += w; } if( div != 0.0f ) (*xj)( j ) = sum / div; else (*xj)( j ) = 0.0f; } #endif return; } /** * Computes values of polynomial response function and puts them into Iresp[]. * * @param c_cc - polynomial degree * @param c - polynomial coefficients * @param cc - number of luma levels (256 for 8-bits data) * @param Iresp - output array with data of the inverse response function */ void HDRCaptureMitsunaga::getInverseResponseArray( int c_cc, float* c, int cc, float* Iresp ) { for( int m = 0; m < cc; m++) { // float v = float(m); float v = float(m)/(float)cc; float sum = 0; for( int i = 0; i < (c_cc+1); i++) { sum += ( pow( v, (c_cc - i)) * c[i]); } Iresp[m] = sum; } return; } /** * Computes the derivative of inverse response function and puts the values into w[]. * * @param c_cc - polynomial degree * @param c - polynomial coefficients * @param cc - number of luma levels (256 for 8-bits data) * @param Iresp - output array with derivative of the inverse response function */ void HDRCaptureMitsunaga::getDerivativeOfInverseResponseArray( int c_cc, float* c, int cc, float* w ) { float der; for(int m = 0; m < cc; m++) { der = 0; // float v = float(m); float v = float(m)/(float)cc; for( int i = 0; i < c_cc; i++) { der += ( pow( (float)v, (c_cc - i - 1)) * c[i] * (float)(c_cc-i)); } w[m] = der; } return; } /** * Generates a set of random pixels to speed-up calculations of inverse response, * only these pixels are taken into account. * * @param imgs - input image data for a separate channel * @param no - number of points which will be selected */ int HDRCaptureMitsunaga::randomPixels( const ExposureList &imgs, unsigned long no) { if( imgs.size() < 1 ) { VERBOSE_STR << "ERROR: wrong input image list" << endl; return 1; } // frame size unsigned long idx; float N = imgs[0].yi->getCols() * imgs[0].yi->getRows(); for( unsigned long i = 0; i < no; i++) { idx = (unsigned long)( (float)rand() / (float)RAND_MAX * N); pixelList.push_back( idx); } //VERBOSE_STR << "pixelList: " << pixelList.size() << endl; return 0; } /** * Computes camera inverse response function based on algorithm decribed in * Tomoo Mitsunaga and Shree K. Nayar in "Radiometric Self Calibration" paper. * This function uses selected pixels only to compute inverse response. * * @param imgs - list of input images (one channel) and exposure times * @param poly_degree - degree of output inverse response polynomial * @param coef - computed (output) polynomial coeficients (poly_degree+1 for each channel) * @param M - number of color levels (256 for 8-bits channels) * * @return Coefficients of inverse response polynomial stored in coef[poly_degree+1]. */ int HDRCaptureMitsunaga::getResponseFast( const ExposureList &imgs, int poly_degree, float* coef, int M) { // number of exposures (input images) int Q = imgs.size(); if( Q < 2) { fprintf( stderr, "WARNING: not enough input images (mitsunaga_getResponse())\n"); return -1; } // frame size int P = imgs[0].yi->getCols() * imgs[0].yi->getRows(); float R; // exposure ratio int N = poly_degree; // degree of polynomial float c[N][NR_LINEAR_EQUATIONS_MAX]; for( int e = 0; e < N; e ++) for( int i = 0; i < N; i++) c[e][i] = 0; float b[N]; for( int e = 0; e < N; e ++) b[e] = 0; float M1, M2; float Imax = 1.0; list::iterator i; for( int q = 0; q < (Q-1); q++) { // for all images - 1 R = imgs[q].ti / imgs[q+1].ti; for( i = pixelList.begin(); i != pixelList.end(); ++i) { M1 = (*imgs[q].yi)(*i); M2 = (*imgs[q+1].yi)(*i); float an = pow( M1, N) - R * pow( M2, N); for( int e = 0; e < N; e ++) { // for all equations float aj = pow( M1, e) - R * pow( M2, e); for( int i = 0; i < N; i++) { float ai = pow( M1, i) - R * pow( M2, i); c[e][i] += (( ai - an) * ( aj - an)); } b[e] += (-an * Imax * ( aj - an)); } } } MitsunagaNumerical::linearEquationsSystem( N, c, b); for( int e = 0; e < N; e ++) { if( isnan(b[e])) { fprintf(stderr, "ERROR: NaN cooefficient (mitsunaga_getResponse())\n"); return 0; } } float cn; float sum = 0; for( int e = 0; e < N; e ++) sum += b[e]; cn = 1.0 - sum; coef[0] = cn; for(int i = (N-1); i >=0; i--) { coef[N-i] = b[i]; } return N; } pfstools-2.2.0/src/camera/dcraw2hdrgen.in0000664000701400070140000000451514105165614017007 0ustar rkm38rkm38#!@BASH_PATH@ # # This file is a part of PFS CALIBRATION package. # ---------------------------------------------------------------------- # Copyright (C) 2004 Grzegorz Krawczyk, # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # ---------------------------------------------------------------------- # This program by Georg Zotti , derived from jpeg2hdrgen # # $Id: dcraw2hdrgen,v 1.3 2013/11/10 21:10:02 rafm Exp $ LC_NUMERIC=POSIX # ensure dot is used as the decimal separator export LC_NUMERIC LC_ALL=POSIX # apparently this is necessary on Gentoo export LC_ALL DCRAW="dcraw -i -v" # program for extracting exposure info from "raw" images TEST_DCRAW=`which dcraw`; if [ "$TEST_DCRAW" = "" ]; then echo "Program 'dcraw' is required to run this script." echo "Install appropriate software, for example from:" echo "http://www.cybercom.net/~dcoffin/dcraw/" exit 1; fi #Note: Double backslash MUST be put in front of each $ sign AWK_PROGRAM=`cat <|-\fBr\fR ] [-\fBc\fR (min|max)|--\fBcrop\fR (min|max)] [-\fBs\fR val|--\fBsensitivity\fR val] [-\fBf\fR|--\fBfail-no-match\fR] [--\fBdisplay-matches\fR|-\fBd\fR] [--\fBverbose\fR] [--\fBhelp\fR] .SH DESCRIPTION Use this command to align image stack, for example for HDR merging. The command will use a feature-point-based matching method to find a homographic transformation that maps all images in the stack to the coordinates of the reference image. Then, it will output transformed (aligned) images, all of the same resolution. The resolution of input images may vary. The command can eliminate a substantial misalignment due to camera rotation (along any axis) and slight movement. .PP The method is essentially similar to the one presented in the paper: Tomaszewska, Anna, and Radoslaw Mantiuk. "Image Registration for Multi-Exposure High Dynamic Range Image Acquisition." In International Conferences in Central Europe on Computer Graphics, Visualization and Computer Vision, pp. 49-56, 2007. However, the code is not based on the authors' implementation. .PP The images are matched pairwise in the same order as they are send to the command. Make sure that all consecutive image pairs contain overlapping parts. When aligning multiple exposure, use \fBpfsinme\fR command, which sorts exposures according to the shutter speed and thus improves matching between image pairs. The command will assume no misalignment (or fail with \fB-f\fR option) if there is no overlap, or it cannot find a sufficient number of features in one of the pairs. .PP Homographic alignment will not eliminate all misalignment artefacts. In particular, it will not eliminate ghosting due to moving objects. The alignment algorithm assumes that the effect of parallax is negligible. Therefore, if the camera moves rather than rotates and there are objects close to the camera, they will most likely remain misaligned. .SH OPTIONS .TP --\fBreference\fR , -\fBr\fR Index of the reference image. The first image has index 1. Default: 1 (first image). .TP --\fBdisplay-matches\fR, -\fBd\fR Display matching key-points for each image pair. This option is useful for inspecting what went wrong with key-point matching. .TP --\fBcrop\fR min|max, -\fBc\fR min|max Crop either to the image that contains all pixels (max) or to the image that contains only the pixels that overlap in all exposures (min). The latter option will eliminate black (empty) pixels from all exposures. .TP --\fBsensitivity\fR value, -\fBs\fR value Higher sensitivity will result in detecting more feature points and potentially a better alignment but also longer computation time. "value" parameter must be in the range from 1 to 10. Default: 5. .TP --\fBfail-no-match\fR, -\fBf\fR If the option is present, the program will exit with a fail status when no match was found for one or more of image pairs. Otherwise, the program will assume that the failed image pair is perfectly aligned (assume identity matrix as a homograhy). .SH EXAMPLES .TP pfsin img1.exr img2.exr | pfsalign -v -r 2 | pfsview .IP Align two EXR images using the second image (img2.exr) as a reference. Then, preview the result. .TP pfsinme *.CR2 | pfssize --maxx 1200 | pfsalign -v -c min | pfshdrcalibrate -r linear -v --bpp 16 | pfsout result.exr .IP Align a stack of RAW image exposures (from Canon camera), resize them so that the width is equal or less 1200 pixels, align exposures, and merge them to an HDR image. .SH "SEE ALSO" .BR pfsin (1) .SH BUGS The current implementation is rather inefficient in terms of memory management and can consume a considerable amount of memory. This is due to the pfstools architecture, which requires that all aligned frames must be loaded into memory. It should be possible to align 3-4 10 mega-pixels images for 1.5GB of available memory. .PP Please report bugs and comments to the pfstools discussion group (http://groups.google.com/group/pfstools). pfstools-2.2.0/src/camera/responses.h0000664000701400070140000001211314105165614016270 0ustar rkm38rkm38/** * @brief Standard response functions * * * This file is a part of PFS CALIBRATION package. * ---------------------------------------------------------------------- * Copyright (C) 2004 Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Grzegorz Krawczyk, * * $Id: responses.h,v 1.5 2011/02/24 17:35:59 ihrke Exp $ */ #ifndef _std_responses_h_ #define _std_responses_h_ #include #include #include /** * @brief Container for images taken with different exposures */ class Exposure { public: float ti; // exposure value (eg time) - including iso and apperture float exposure_time; // exposure time float iso; // sensor gain iso value float aperture; pfs::Array2D* yi; // exposed pixel value (camera output) // to be able to sort exposures using stl static bool msort( Exposure const & a, Exposure const & b) { return a.ti < b.ti; } }; /** * @brief Container for a list of exposures */ typedef std::vector ExposureList; /** * @brief Weighting function with gaussian distribution * * @param w [out] weights (array size of M) * @param M number of camera output levels * @param Mmin minimum registered camera output level * @param Mmax maximum registered camera output level * @param sigma sigma value for gaussian */ void weightsGauss( float* w, int M, int Mmin, int Mmax, float sigma ); /** * @brief Composite weighting function - dark regions * gamma curve, dark to medium - Gaussian, medium to high linear * topmost values steep Gaussian fall-off * * @param w [out] weights (array size of M) * @param M number of camera output levels * @param Mmin minimum registered camera output level * @param Mmax maximum registered camera output level * @param sigma sigma value for gaussian */ void weightsComposite( float* w, int M, int Mmin, int Mmax, float sigma ); /** * @brief Create gamma response function * * @param I [out] camera response function (array size of M) * @param M number of camera output levels */ void responseGamma( float* I, int M ); /** * @brief Create linear response function * * @param I [out] camera response function (array size of M) * @param M number of camera output levels */ void responseLinear( float* I, int M ); /** * @brief Create logarithmic response function * * @param I [out] camera response function (array size of M) * @param M number of camera output levels */ void responseLog10( float* I, int M ); /** * @brief Save response curve to a MatLab file for further reuse * * @param file file handle to save response curve * @param I camera response function (array size of M) * @param M number of camera output levels * @param name matrix name for use in Octave or Matlab */ void responseSave( FILE* file, const float* I, int M, const char* name); /** * @brief Save response curve to a MatLab file for further reuse * * @param file file handle to save response curve * @param w weights (array size of M) * @param M number of camera output levels * @param name matrix name for use in Octave or Matlab */ void weightsSave( FILE* file, const float* w, int M, const char* name); /** * @brief Load response curve (saved with responseSave();) * * @param file file handle to load response curve * @param I [out] camera response function (array size of M) * @param M number of camera output levels * @return false means file has different output levels or is wrong for some other reason */ bool responseLoad( FILE* file, float* I, int M); /** * @brief Load response curve (saved with responseSave();) * * @param file file handle to save response curve * @param w [out] weights (array size of M) * @param M number of camera output levels * @return false means file has different output levels or is wrong for some other reason */ bool weightsLoad( FILE* file, float* w, int M); /** * @brief Fill missing (zero) values in the response curve * * The weighting function will be set to the minimum weight value for * the places where the response curve was filled in. * * @param I [in/out] camera response function (array size of M) * @param w [in/out] weighting function (array size of M) * @param M number of camera output levels * @return number of filled values in the response curve */ int responseFillGaps( float* I, float* w, int M ); #endif pfstools-2.2.0/src/camera/nrutil.h0000664000701400070140000000641014105165614015567 0ustar rkm38rkm38#ifndef _NR_UTILS_H_ #define _NR_UTILS_H_ static float sqrarg; #define SQR(a) ((sqrarg=(a)) == 0.0 ? 0.0 : sqrarg*sqrarg) static double dsqrarg; #define DSQR(a) ((dsqrarg=(a)) == 0.0 ? 0.0 : dsqrarg*dsqrarg) static double dmaxarg1,dmaxarg2; #define DMAX(a,b) (dmaxarg1=(a),dmaxarg2=(b),(dmaxarg1) > (dmaxarg2) ?\ (dmaxarg1) : (dmaxarg2)) static double dminarg1,dminarg2; #define DMIN(a,b) (dminarg1=(a),dminarg2=(b),(dminarg1) < (dminarg2) ?\ (dminarg1) : (dminarg2)) static float maxarg1,maxarg2; #define FMAX(a,b) (maxarg1=(a),maxarg2=(b),(maxarg1) > (maxarg2) ?\ (maxarg1) : (maxarg2)) static float minarg1,minarg2; #define FMIN(a,b) (minarg1=(a),minarg2=(b),(minarg1) < (minarg2) ?\ (minarg1) : (minarg2)) static long lmaxarg1,lmaxarg2; #define LMAX(a,b) (lmaxarg1=(a),lmaxarg2=(b),(lmaxarg1) > (lmaxarg2) ?\ (lmaxarg1) : (lmaxarg2)) static long lminarg1,lminarg2; #define LMIN(a,b) (lminarg1=(a),lminarg2=(b),(lminarg1) < (lminarg2) ?\ (lminarg1) : (lminarg2)) static int imaxarg1,imaxarg2; #define IMAX(a,b) (imaxarg1=(a),imaxarg2=(b),(imaxarg1) > (imaxarg2) ?\ (imaxarg1) : (imaxarg2)) static int iminarg1,iminarg2; #define IMIN(a,b) (iminarg1=(a),iminarg2=(b),(iminarg1) < (iminarg2) ?\ (iminarg1) : (iminarg2)) #define SIGN(a,b) ((b) >= 0.0 ? fabs(a) : -fabs(a)) #if defined(__STDC__) || defined(ANSI) || defined(NRANSI) /* ANSI */ void nrerror(const char error_text[]); float *vector(long nl, long nh); int *ivector(long nl, long nh); unsigned char *cvector(long nl, long nh); unsigned long *lvector(long nl, long nh); double *dvector(long nl, long nh); float **matrix(long nrl, long nrh, long ncl, long nch); double **dmatrix(long nrl, long nrh, long ncl, long nch); int **imatrix(long nrl, long nrh, long ncl, long nch); float **submatrix(float **a, long oldrl, long oldrh, long oldcl, long oldch, long newrl, long newcl); float **convert_matrix(float *a, long nrl, long nrh, long ncl, long nch); float ***f3tensor(long nrl, long nrh, long ncl, long nch, long ndl, long ndh); void free_vector(float *v, long nl, long nh); void free_ivector(int *v, long nl, long nh); void free_cvector(unsigned char *v, long nl, long nh); void free_lvector(unsigned long *v, long nl, long nh); void free_dvector(double *v, long nl, long nh); void free_matrix(float **m, long nrl, long nrh, long ncl, long nch); void free_dmatrix(double **m, long nrl, long nrh, long ncl, long nch); void free_imatrix(int **m, long nrl, long nrh, long ncl, long nch); void free_submatrix(float **b, long nrl, long nrh, long ncl, long nch); void free_convert_matrix(float **b, long nrl, long nrh, long ncl, long nch); void free_f3tensor(float ***t, long nrl, long nrh, long ncl, long nch, long ndl, long ndh); #else /* ANSI */ /* traditional - K&R */ void nrerror(); float *vector(); float **matrix(); float **submatrix(); float **convert_matrix(); float ***f3tensor(); double *dvector(); double **dmatrix(); int *ivector(); int **imatrix(); unsigned char *cvector(); unsigned long *lvector(); void free_vector(); void free_dvector(); void free_ivector(); void free_cvector(); void free_lvector(); void free_matrix(); void free_submatrix(); void free_convert_matrix(); void free_dmatrix(); void free_imatrix(); void free_f3tensor(); #endif /* ANSI */ #endif /* _NR_UTILS_H_ */ pfstools-2.2.0/src/camera/jpeg2hdrgen.in0000664000701400070140000000622414105165614016633 0ustar rkm38rkm38#!@BASH_PATH@ # # This file is a part of PFS CALIBRATION package. # ---------------------------------------------------------------------- # Copyright (C) 2004 Grzegorz Krawczyk, # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # ---------------------------------------------------------------------- # # $Id: jpeg2hdrgen,v 1.9 2013/11/10 21:10:02 rafm Exp $ LC_NUMERIC=POSIX # ensure dot is used as the decimal separator export LC_NUMERIC LC_ALL=POSIX # apparently this is necessary on Gentoo export LC_ALL JHEAD="jhead" # program for extracting exif info from jpegs IDENTIFY="identify" # alternative program from ImageMagics TEST_IDENTIFY=`which $IDENTIFY 2>/dev/null` || TEST_JHEAD=`which $JHEAD 2>/dev/null`; if [ -z "$TEST_JHEAD" ] && [ -z "$TEST_IDENTIFY" ]; then echo "Either 'jhead' or 'identify' (part of ImageMagic) is required to run this script." >&2 echo "Install appropriate software, for example from:" >&2 echo "http://www.sentex.net/~mwandel/jhead/" >&2 exit 1; fi #Note: Double backslash MUST be put in front of each $ sign AWK_PROGRAM_JHEAD=`cat < # # $Id: pfsplotresponse,v 1.1 2011/03/04 10:00:59 rafm Exp $ if test -z $1 || test "$1" = "--help"; then cat < [ ...] See the man page for more information. EOF exit 1 fi if ! which gnuplot >/dev/null; then cat < [ [...]] .SH DESCRIPTION Plot camera response curve generated by \fIpfshdrcalibrate\fR using Gnuplot. The three curves show separate responses for red, green and blue color channels. The fourth curve is the weighting function used to merge the exposures. .SH EXAMPLES .TP pfsplotresponse canon.resp Plot the camera response curve from canon.resp. .SH "SEE ALSO" .BR pfshdrcalibrate (1) .SH BUGS Please report bugs and comments on implementation to the discussion group http://groups.google.com/group/pfstools pfstools-2.2.0/src/camera/jpeg2hdrgen.10000664000701400070140000000160014105165614016356 0ustar rkm38rkm38.TH "jpeg2hdrgen" 1 .SH NAME jpeg2hdrgen \- Parse EXIF information in given JPEG files and output a hdrgen script for generation of HDR images. .SH SYNOPSIS .B jpeg2hdrgen [...] .SH DESCRIPTION This program can be used to automatically create a hdrgen script for use with software creating HDR images. The exposure information is extracted from EXIF data contained in jpeg images created using digital cameras. If this script doesn't recognise the exposure settings from the images taken by your camera, send me an output of a `jhead your_image.jpg` program. .SH EXAMPLES .TP jpeg2hdrgen IMG_0001.JPG IMG_0002.JPG IMG_0003.JPG > sample.hdrgen Creates a sample.hdrgen script for given images. .SH "SEE ALSO" .BR pfsinhdrgen (1) .BR pfshdrcalibrate (1) .SH BUGS Please report bugs and comments on implementation to Grzegorz Krawczyk . pfstools-2.2.0/src/camera/pfsmergehdr.cpp0000664000701400070140000000472614105165614017123 0ustar rkm38rkm38/** * @brief Create an HDR image directly from JPEG files * * * This file is a part of PFS CALIBRATION package. * ---------------------------------------------------------------------- * Copyright (C) 2004 Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Wenxiang Ying * * $Id: pfsmergehdr.cpp,v 1.1 2007/03/28 15:18:29 gkrawczyk Exp $ */ #include #include #include #include #include #include using namespace std; //--- standard PFS stuff #define PROG_NAME "pfsmergehdr" bool verbose = false; void printHelp() { fprintf( stderr, PROG_NAME ": \n" "\t[--calibration ] [--luminance]\n" "\t[--response ] [--response-file ] \n" "\t[--save-response ] \n" "\t[--multiplier ] \n" "\t[--bpp ] \n" "\t[--verbose] [--help]\n" "See man page for more information.\n" ); } int main( int argc, char* argv[] ) { static struct option cmdLineOptions[] = { { "help", no_argument, NULL, 'h' }, { "verbose", no_argument, NULL, 'v' }, { NULL, 0, NULL, 0 } }; int optionIndex = 0; while( 1 ) { int c = getopt_long (argc, argv, "hv", cmdLineOptions, &optionIndex); if( c == -1 ) break; switch( c ) { case 'v': verbose = true; break; case 'h': case '?': case ':': printHelp(); return -1; } } //--- verbose information and load initialization data // use this stream to output useful information for a user, they will //see it if they lunch the program with -v option. do not output //information in any other way VERBOSE_STR << "verbose information" << endl; return 0; } pfstools-2.2.0/src/camera/pfsinme.10000664000701400070140000000216714105165614015631 0ustar rkm38rkm38.TH "pfsinme" 1 .SH NAME pfsinme \- Read image files containing multiple exposures to be merged into an HDR image .SH SYNOPSIS .B pfsinme [] .SH DESCRIPTION Use this program to read multiple exposure JPEG or RAW images. The output of this command should be piped to \fIpfshdrcalibrate\fR to recover a camera response or merge images into an HDR image. All listed files must be the same type, that is mixing JPEG and RAW images is not allowed. You can use wildcards. This command is a convinience wrapper for \fIjpeg2hdrgen\fR, \fIdcraw2hdrgen\fR and \fIpfsinhdrgen\fR, which skips the step of generating an \fIhdrgen\fR file .SH EXAMPLES .TP pfsinme *.jpg | pfsrotate -r | pfshdrcalibrate -s response.m -v Read all JPEG images in the current directory, rotate them and merge into an HDR image .SH "SEE ALSO" .BR pfshdrcalibrate (1) .BR jpeg2hdrgen (1) .BR dcraw2hdrgen (1) .BR pfsinhdrgen (1) .SH BUGS The command cannot handle files with a space in the file name because of the pfsinhdrgen limitation. .PP Please report bugs and comments on implementation to the discussion group http://groups.google.com/group/pfstools pfstools-2.2.0/src/camera/pfsinhdrgen.10000664000701400070140000000306414105165614016474 0ustar rkm38rkm38.TH "pfsinhdrgen" 1 .SH NAME pfsinhdrgen \- Create a pfs stream with different exposures defined by hdrgen script. .SH SYNOPSIS .B pfsinhdrgen .SH DESCRIPTION This program reads files defined in given hdrgen script and outputs them in a PFS stream. Each frame has a tag BV with corresponding brightness value (APEX standard). Larger BV corresponds to less exposure (darker image), necessary to capture a bright scene. The generated PFS stream is to be used with photo-metric calibration software and with generators of HDR images. HDRGEN script file is a plain text file in which each line contains: 0 In most cases it is convenient to use the jpeg2hdrgen program to create such a file automatically. In case it could not parse the exif information from jpeg files, it is necessary to create this file by hand. Below are the sample contents of such file: <--- cut here: sample.hdrgen /var/tmp/images/img08.jpg 4 2.8 100 0 /var/tmp/images/img09.jpg 58.8235 2.8 100 0 /var/tmp/images/img10.jpg 76.9231 2.8 100 0 /var/tmp/images/img11.jpg 322.581 2.8 100 0 /var/tmp/images/img12.jpg 400 2.8 100 0 <--- cut here: sample.hdrgen .SH EXAMPLES .TP pfsinhdrgen sample.hdrgen | pfsview View exposures defined in sample.hdrgen script. .SH "SEE ALSO" .BR jpeg2hdrgen (1) .BR pfshdrcalibrate (1) .BR pfsinme (1) .SH BUGS The command cannot handle files with a space in the name. .PP Please report bugs and comments on implementation to Grzegorz Krawczyk . pfstools-2.2.0/src/camera/mitsunaga99.h0000664000701400070140000000555514105165614016435 0ustar rkm38rkm38/** * @brief Algorithm for automatic HDR images capture. * * * This file is a part of PFS CALIBRATION package. * ---------------------------------------------------------------------- * Copyright (C) 2006 Radoslaw Mantiuk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Radoslaw Mantiuk, * */ #ifndef _mitsunaga99_h_ #define _mitsunaga99_h_ #include using namespace std; enum { R, G, B }; #define CH 3 #define MITSUNAGA_SAMPLES_NO 50000 class HDRCaptureMitsunaga { private: list pixelList; public: int capture( ExposureList &imgsR, /* input red images */ ExposureList &imgsG, /* input green images */ ExposureList &imgsB, /* input blue images */ int M, /* ?? */ int poly_degree, /* degree of polynomial approximation */ pfs::Array2D* Xj, /* output HDR image red */ pfs::Array2D* Yj, /* output HDR image green */ pfs::Array2D* Zj, /* output HDR image blue */ unsigned long samples_no, /* number of randomly selected pixels to use */ float* Ir, /* response curve red in/out depending in comp_response */ float* Ig, /* response curve green in/out depending in comp_response */ float* Ib, /* response curve blue in/out depending in comp_response */ float* w, /* weight function for HDR computation */ int comp_response /* 0 - do not compute response 1 - compute response */ ); int getResponse( const ExposureList &imgs, int poly_degree, float* coef, int M ); void applyResponse( pfs::Array2D* xj, ExposureList &imgs, float* Iresp, int M, float* w) ; void getInverseResponseArray( int c_cc, float* c, int cc, float* Iresp ); void getDerivativeOfInverseResponseArray( int c_cc, float* c, int cc, float* w ); int randomPixels( const ExposureList &imgs, unsigned long no); int getResponseFast( const ExposureList &imgs, int poly_degree, float* coef, int M); }; #endif pfstools-2.2.0/src/camera/CMakeLists.txt0000664000701400070140000000340414105165614016641 0ustar rkm38rkm38include_directories ("${PROJECT_BINARY_DIR}/" "${PROJECT_SOURCE_DIR}/src/pfs" "${CMAKE_CURRENT_SOURCE_DIR}") link_directories("${PROJECT_SOURCE_DIR}/src/pfs") set(TRG pfshdrcalibrate) add_executable(${TRG} ${TRG}.cpp responses.cpp robertson02.cpp mitsunaga99.cpp mitsunaga99_numerical.cpp nrutil.cpp "${GETOPT_OBJECT}") target_link_libraries(${TRG} pfs) install (TARGETS ${TRG} DESTINATION bin) install (FILES ${TRG}.1 DESTINATION ${MAN_DIR}) if( OpenCV_FOUND AND EXIF_FOUND ) set(TRG pfsalign) add_executable(${TRG} ${TRG}.cpp "${GETOPT_OBJECT}") target_link_libraries(${TRG} pfs ${OpenCV_LIBS} ${EXIF_LIBRARIES}) include_directories( ${EXIF_INCLUDE_DIRS} ) install (TARGETS ${TRG} DESTINATION bin) install (FILES ${TRG}.1 DESTINATION ${MAN_DIR}) endif( OpenCV_FOUND AND EXIF_FOUND ) # Shell scripts set(SCRIPTS dcraw2hdrgen jpeg2hdrgen pfsinme pfsplotresponse pfsinhdrgen) foreach(SCRIPT ${SCRIPTS}) # Replace the tag with the path to bash file(READ ${CMAKE_CURRENT_SOURCE_DIR}/${SCRIPT}.in file_content) string(REGEX REPLACE "@BASH_PATH@" "${BASH_EXECUTABLE}" file_content "${file_content}") string(REGEX REPLACE "@PERL_PATH@" "${PERL_EXECUTABLE}" file_content "${file_content}") file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/${SCRIPT}" "${file_content}") install (FILES "${CMAKE_CURRENT_BINARY_DIR}/${SCRIPT}" PERMISSIONS OWNER_EXECUTE GROUP_EXECUTE WORLD_EXECUTE OWNER_WRITE WORLD_READ GROUP_READ OWNER_READ DESTINATION bin) install (FILES ${SCRIPT}.1 DESTINATION ${MAN_DIR}) endforeach(SCRIPT) install (FILES pfs_split_exposures.py pfs_automerge PERMISSIONS OWNER_EXECUTE GROUP_EXECUTE WORLD_EXECUTE OWNER_WRITE WORLD_READ GROUP_READ OWNER_READ DESTINATION bin) install (FILES pfs_automerge.1 DESTINATION ${MAN_DIR}) pfstools-2.2.0/src/camera/dcraw2hdrgen.10000664000701400070140000000204114105165614016531 0ustar rkm38rkm38.TH "dcraw2hdrgen" 1 .SH NAME dcraw2hdrgen \- Parse information in given Digital Camera RAW files and output a hdrgen script for generation of HDR images. .SH SYNOPSIS .B dcraw2hdrgen [...] .SH DESCRIPTION This program can be used to automatically create an hdrgen script for use with software creating HDR images. The exposure information is extracted from RAW files using the `dcraw` tool. If this script doesn't recognise the exposure settings from the images taken by your camera, please verify if dcraw supports your camera: `dcraw -i -v your_image.cr2`. You should see a detailed information on shot settings including exposure time. Maybe compiling the newest version will help. .SH EXAMPLES .TP dcraw2hdrgen IMG_0001.CR2 IMG_0002.CR2 IMG_0003.CR2 > sample.hdrgen Creates a sample.hdrgen script for given images. .SH "SEE ALSO" .BR dcraw (1) .BR pfsindcraw (1) .BR pfsinhdrgen (1) .BR pfshdrcalibrate (1) .SH BUGS Please report bugs and comments on implementation to Grzegorz Krawczyk . pfstools-2.2.0/src/camera/nrutil.cpp0000664000701400070140000001757714105165614016142 0ustar rkm38rkm38//#if defined(__STDC__) || defined(ANSI) || defined(NRANSI) /* ANSI */ #include #include #include #define NR_END 1 #define FREE_ARG char* void nrerror(const char error_text[]) /* Numerical Recipes standard error handler */ { fprintf(stderr,"Numerical Recipes run-time error...\n"); fprintf(stderr,"%s\n",error_text); fprintf(stderr,"...now exiting to system...\n"); exit(1); } float *vector(long nl, long nh) /* allocate a float vector with subscript range v[nl..nh] */ { float *v; v=(float *)malloc((size_t) ((nh-nl+1+NR_END)*sizeof(float))); if (!v) nrerror("allocation failure in vector()"); return v-nl+NR_END; } int *ivector(long nl, long nh) /* allocate an int vector with subscript range v[nl..nh] */ { int *v; v=(int *)malloc((size_t) ((nh-nl+1+NR_END)*sizeof(int))); if (!v) nrerror("allocation failure in ivector()"); return v-nl+NR_END; } unsigned char *cvector(long nl, long nh) /* allocate an unsigned char vector with subscript range v[nl..nh] */ { unsigned char *v; v=(unsigned char *)malloc((size_t) ((nh-nl+1+NR_END)*sizeof(unsigned char))); if (!v) nrerror("allocation failure in cvector()"); return v-nl+NR_END; } unsigned long *lvector(long nl, long nh) /* allocate an unsigned long vector with subscript range v[nl..nh] */ { unsigned long *v; v=(unsigned long *)malloc((size_t) ((nh-nl+1+NR_END)*sizeof(long))); if (!v) nrerror("allocation failure in lvector()"); return v-nl+NR_END; } double *dvector(long nl, long nh) /* allocate a double vector with subscript range v[nl..nh] */ { double *v; v=(double *)malloc((size_t) ((nh-nl+1+NR_END)*sizeof(double))); if (!v) nrerror("allocation failure in dvector()"); return v-nl+NR_END; } float **matrix(long nrl, long nrh, long ncl, long nch) /* allocate a float matrix with subscript range m[nrl..nrh][ncl..nch] */ { long i, nrow=nrh-nrl+1,ncol=nch-ncl+1; float **m; /* allocate pointers to rows */ m=(float **) malloc((size_t)((nrow+NR_END)*sizeof(float*))); if (!m) nrerror("allocation failure 1 in matrix()"); m += NR_END; m -= nrl; /* allocate rows and set pointers to them */ m[nrl]=(float *) malloc((size_t)((nrow*ncol+NR_END)*sizeof(float))); if (!m[nrl]) nrerror("allocation failure 2 in matrix()"); m[nrl] += NR_END; m[nrl] -= ncl; for(i=nrl+1;i<=nrh;i++) m[i]=m[i-1]+ncol; /* return pointer to array of pointers to rows */ return m; } double **dmatrix(long nrl, long nrh, long ncl, long nch) /* allocate a double matrix with subscript range m[nrl..nrh][ncl..nch] */ { long i, nrow=nrh-nrl+1,ncol=nch-ncl+1; double **m; /* allocate pointers to rows */ m=(double **) malloc((size_t)((nrow+NR_END)*sizeof(double*))); if (!m) nrerror("allocation failure 1 in matrix()"); m += NR_END; m -= nrl; /* allocate rows and set pointers to them */ m[nrl]=(double *) malloc((size_t)((nrow*ncol+NR_END)*sizeof(double))); if (!m[nrl]) nrerror("allocation failure 2 in matrix()"); m[nrl] += NR_END; m[nrl] -= ncl; for(i=nrl+1;i<=nrh;i++) m[i]=m[i-1]+ncol; /* return pointer to array of pointers to rows */ return m; } int **imatrix(long nrl, long nrh, long ncl, long nch) /* allocate a int matrix with subscript range m[nrl..nrh][ncl..nch] */ { long i, nrow=nrh-nrl+1,ncol=nch-ncl+1; int **m; /* allocate pointers to rows */ m=(int **) malloc((size_t)((nrow+NR_END)*sizeof(int*))); if (!m) nrerror("allocation failure 1 in matrix()"); m += NR_END; m -= nrl; /* allocate rows and set pointers to them */ m[nrl]=(int *) malloc((size_t)((nrow*ncol+NR_END)*sizeof(int))); if (!m[nrl]) nrerror("allocation failure 2 in matrix()"); m[nrl] += NR_END; m[nrl] -= ncl; for(i=nrl+1;i<=nrh;i++) m[i]=m[i-1]+ncol; /* return pointer to array of pointers to rows */ return m; } float **submatrix(float **a, long oldrl, long oldrh, long oldcl, long oldch, long newrl, long newcl) /* point a submatrix [newrl..][newcl..] to a[oldrl..oldrh][oldcl..oldch] */ { long i,j,nrow=oldrh-oldrl+1,ncol=oldcl-newcl; float **m; /* allocate array of pointers to rows */ m=(float **) malloc((size_t) ((nrow+NR_END)*sizeof(float*))); if (!m) nrerror("allocation failure in submatrix()"); m += NR_END; m -= newrl; /* set pointers to rows */ for(i=oldrl,j=newrl;i<=oldrh;i++,j++) m[j]=a[i]+ncol; /* return pointer to array of pointers to rows */ return m; } float **convert_matrix(float *a, long nrl, long nrh, long ncl, long nch) /* allocate a float matrix m[nrl..nrh][ncl..nch] that points to the matrix declared in the standard C manner as a[nrow][ncol], where nrow=nrh-nrl+1 and ncol=nch-ncl+1. The routine should be called with the address &a[0][0] as the first argument. */ { long i,j,nrow=nrh-nrl+1,ncol=nch-ncl+1; float **m; /* allocate pointers to rows */ m=(float **) malloc((size_t) ((nrow+NR_END)*sizeof(float*))); if (!m) nrerror("allocation failure in convert_matrix()"); m += NR_END; m -= nrl; /* set pointers to rows */ m[nrl]=a-ncl; for(i=1,j=nrl+1;i|-\fBd\fR ] [--\fBformat\fR (exr|hdr)|-\fBf\fR (exr|hdr)] .SH DESCRIPTION This command can be used to mass-merge multiple HDR images from RAW files, where each exposure stack is stored in a separate directory. pfs_split_exposures can be used to automatically group RAW images into individual directories. .SH OPTIONS .TP --\fBalign\fR, -\fBa\fR When set, the images will be aligned with pfsalign before merging. .TP --\fBdest\fR , -\fBd\fR Write merged HDR images into the directory . Default: "merged". .TP --\fBformat\fR (exr|hdr)|-\fBf\fR (exr|hdr) Store HDR images in OpenEXR format (exr) or Radiance RGBE (hdr). Default: "exr". .SH "SEE ALSO" .BR pfsinme (1), pfs_split_exposures (1) .SH BUGS Please report bugs and comments to the pfstools discussion group (http://groups.google.com/group/pfstools). pfstools-2.2.0/src/camera/pfs_split_exposures.py0000664000701400070140000000664614105165614020606 0ustar rkm38rkm38#!/usr/bin/python3 # Split the HDR exposure files into directories based on the capture time # work in progress. Currently works only with Sony's ARW files import os import sys import subprocess import re import argparse parser = argparse.ArgumentParser(description='Move images files that belong to the same HDR exposure sequence into separate directories.') parser.add_argument('dir', metavar='dir', nargs='?', help='the directory with images files', default='.') parser.add_argument('--threshold', '-t', type=float, dest='threshold', default=5, help='Exposures taken less than "threshold" seconds apart will belong to the same exposure') parser.add_argument('--simulate', '-s', dest='simulate', action='store_true', help='Do not perform any action but display how files would be split') parser.add_argument('--minexp', '-m', dest='minexp', default=3, help='The minimum number of exposures in each exposure stack. If there are fewer exposures than that number, files will not be moved.') parser.add_argument('--basename', '-b', dest='basename', default='hdr', help='The base name for each created directory with an exposure stack. By default the directories will be hdr_000, hdr_001, ...') args = parser.parse_args() print("Searching for HDR exposure stacks in the directory '{}'".format(args.dir)) if args.simulate: print( ' Simulation mode - no action will be performed' ) def move_exp_to_dir( es, stack_no ): dir_name='{0}_{1:03d}'.format(args.basename, stack_no) if not args.simulate: os.makedirs(dir_name,exist_ok=True) for file in es: print( 'Moving {} to {}'.format(file, os.path.join(dir_name,file)) ) if not args.simulate: os.rename(file, os.path.join(dir_name,file)) prev_capture_time=None prev_shutter=None prev_filename=None exp_stack=list() stack_no=1 for filename in os.listdir(args.dir): if filename[-4:] == '.ARW' or filename[-4:] == '.CR2': cp = subprocess.run( ['dcraw', '-v', '-i', '{}'.format(filename)], stdout=subprocess.PIPE, encoding="utf-8" ) lines = cp.stdout.splitlines() capture_time=None for ll in lines: if ll[0:10] == "Timestamp:": ts = re.split('[ ]+', ll) th = ts[4].split(':') capture_time=float(th[0])*3600 + float(th[1])*60 + float(th[2]) if ll[0:8] == "Shutter:": ts = ll.split(' ') th = ts[1].split('/') if len(th)>1: shutter = 1/float(th[1]) else: shutter = float(ts[1]) if capture_time!=None and prev_capture_time!=None: capt_time_diff = capture_time-prev_capture_time-shutter print( ' Files {0} and {1} were captured {2} seconds apart'.format(prev_filename, filename, capt_time_diff)) if( capt_time_diff > args.threshold ): print( '===============') if len(exp_stack) < args.minexp: print( 'Too few exposures to create an exposure stack') else: move_exp_to_dir(exp_stack, stack_no) stack_no = stack_no+1 exp_stack=list() sys.stdout.flush() prev_capture_time = capture_time prev_shutter=shutter prev_filename=filename exp_stack.append(filename) #print( lines )pfstools-2.2.0/src/camera/robertson02.cpp0000664000701400070140000004300714105165614016767 0ustar rkm38rkm38/** * @brief Robertson02 algorithm for automatic self-calibration. * * * This file is a part of PFS CALIBRATION package. * ---------------------------------------------------------------------- * Copyright (C) 2004 Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Grzegorz Krawczyk, * Ivo Ihrke , * * $Id: robertson02.cpp,v 1.11 2011/02/25 13:45:14 ihrke Exp $ */ #include #include #include #include #include #include #include #include #include using namespace std; #define PROG_NAME "robertson02" // maximum iterations after algorithm accepts local minima #define MAXIT 100 // maximum accepted error #define MAX_DELTA 1e-5f extern bool verbose; /* verbose should be declared for each standalone code */ float normalize_rcurve( float *rcurve, int M ); inline float max3( float a[3]) { float max = (a[0]>a[1]) ? a[0] : a[1]; return (a[2]>max) ? a[2] : max; } float get_exposure_compensation( const Exposure &ex ) { return ex.exposure_time * ex.aperture*ex.aperture * ex.iso/3.125f / (1.0592f * 11.4f / 3.125f); } int robertson02_applyResponseRGB( pfs::Array2D *rgb_out[], const ExposureList *imgs[], const float *resp_curve[], const float *weights, int M, const noise_parameters *camera, const float deghosting_threshold) { // number of exposures int N = imgs[0]->size(); // frame size int width = rgb_out[0] -> getCols( ); int height = rgb_out[0] -> getRows( ); // number of saturated pixels int saturated_pixels = 0; float mmax[3]= {1e-30, 1e-30, 1e-30}; /* float pw[M]; for( int i = 0 ; i < M ; i++ ) { pw[i] = weights[i]; } for( int i = 1; i < 10; i++ ) pw[M-i] = 0; float ew[N]; // Per-exposure weight to account for noise charactetistic // For each exposure for( int i = 0 ; i < N ; i++ ) { const Exposure &ex = (*imgs[0])[i]; // TODO: Estimate read-out noise and use for exposure weighting // TODO: Better blending if difference is weights is high // This is a noise-optimial reconstruction assuming that the input is affected only // by the photon noise (no read-out noise), which is most often the case ew[i] = ex.exposure_time; VERBOSE_STR << "Ex: " << i << " exp. time: " << ex.exposure_time << "\t iso: " << ex.iso << "\t aperture: " << ex.aperture << "\t weight: " << ew[i] << std::endl; } */ bool photon_noise_only = false; if( strcmp( camera->camera_name, "Empty" ) == 0 ) { photon_noise_only = true; VERBOSE_STR << "Merging using the Poisson Photon Noise Estimator" << endl; } else VERBOSE_STR << "Merging using the Variance Weighted Estimator" << endl; // For each pixel int skipped_deghost = 0; for( int j = 0; j < width * height; j++ ) { bool saturatedRGB[3] = { false, false, false }; // First compute scene radiances, weights and saturated exposures float X[3][N]; float w[3][N]; bool saturated_exp[N]; fill(saturated_exp, saturated_exp + N, false); bool skip_exp[N]; fill(skip_exp, skip_exp + N, false); float t[N]; float g[N]; for( int i = 0; i < N; i++ ) for( int cc = 0; cc < 3; cc++ ) { const Exposure &ex = (*imgs[cc])[i]; t[i] = ex.exposure_time; g[i] = ex.iso / 100; int m = (int) (*ex.yi)( j ); X[cc][i] = resp_curve[cc][ m ] / ( t[i] * g[i] * camera->color_coefficients[cc] ); if( photon_noise_only ) w[cc][i] = t[i]; else { float var = resp_curve[cc][ m ] * g[i] * camera->color_coefficients[cc] + powf(camera->std_readout * g[i] * camera->color_coefficients[cc], 2) + powf(camera->std_adc * camera->color_coefficients[cc], 2); w[cc][i] = powf( t[i] * g[i] * camera->color_coefficients[cc], 2 ) / var; } // Test for overexposed pixels saturated_exp[i] |= resp_curve[cc][m] >= resp_curve[cc][M-1]; } // Deghosting - find the exposure that could be used as a reference // Currently using middle exposure // TODO: Perform histogram analysis to pick best reference int ref = int(N/2); if( deghosting_threshold != -1 ) { // Compute the number of pixels to be skipped due to deghosting for( int i = 0; i < N; i++ ) if(i != ref && !saturated_exp[ref] && !saturated_exp[i]) { // First compute the Pearson correlation coefficient wrt reference float ref_mean = 0, ref_var = 1e-5, cur_mean = 0, cur_var = 1e-5, cov = 0; for( int cc = 0; cc < 3; cc++ ) { ref_mean += X[cc][ref]; cur_mean += X[cc][i]; } ref_mean /= 3; cur_mean /= 3; for( int cc = 0; cc < 3; cc++ ) { ref_var += powf(X[cc][ref] - ref_mean, 2); cur_var += powf(X[cc][i] - cur_mean, 2); cov += (X[cc][ref] - ref_mean)*(X[cc][i] - cur_mean); } // Next check whether total deviation from the reference is acceptable float std_sum = 0; float deviation_from_ref = 0; for( int cc = 0; cc < 3; cc++ ) { // Reciprocal of the weight is the exposure compensated variance // Square root of this is thus the exposure compensated standard deviation // Assuming a gaussian distribution, 95% of the population should lie 2 stds away from mean std_sum += deghosting_threshold * sqrt(1/w[cc][ref] + 1/w[cc][i]); deviation_from_ref += fabs( (float)(X[cc][ref] - X[cc][i]) ); } // Skipping the Pearson coefficient test as reults are not satisfactory // Fixing a better reference might solve this // float rho = cov / sqrt(ref_var/2 * cur_var/2); // skip_exp[i] = rho < 0.9 || deviation_from_ref > std_sum; skip_exp[i] = deviation_from_ref > std_sum; if( skip_exp[i] ) skipped_deghost++; } } /* // Deghosting if( ref != -1 && !saturated_exp[ref] ) { const Exposure &ex = (*imgs[0])[ref]; float ti = get_exposure_compensation( ex ); float Y_ref = (X[ref*3] + X[ref*3+1] + X[ref*3+2])/3.f; const float noise_level = 0.01; float var_ref = (noise_level*noise_level)*Y_ref/ti; // variance of the photon noise for( int i = 0 ; i < N ; i++ ) { const Exposure &ex = (*imgs[0])[i]; float ti = get_exposure_compensation( ex ); if( saturated_exp[i] ) { // skip_exp[i] = true; continue; } //continue; // Skip deghostig for now float rho = corr_vec3( &X[i*3], &X[ref*3] ); // float chroma = std::max( std::max( X[i*3], X[i*3+1] ), X[i*3+2] ) - std::min( std::min( X[i*3], X[i*3+1] ), X[i*3+2] ); // cerr << rho << " - " << chroma << ", "; // cerr << i << "(" << saturated_exp[i] << "): " << rho << endl; float Y_exp = (X[i*3] + X[i*3+1] + X[i*3+2])/3.f; float var_exp = (noise_level*noise_level)*Y_exp / ti; float ci = 1.96 * sqrt(var_ref+var_exp); // Skip a frame if color correlation too low or difference to reference too high skip_exp[i] = ( rho < 0.9 ) || fabs( Y_exp - Y_ref ) > ci; if( skip_exp[i] ) skipped_deghost++; } } int skip_bits = 0; for( int i = 0 ; i < N ; i++ ) { if( !skip_exp[i] ) skip_bits++; // |= (1< getCols( ); int height = xj -> getRows( ); // number of saturated pixels int saturated_pixels = 0; // cerr << "M = " << M << endl; // cerr << "W[0] = " << weights[0] << endl; // cerr << "W[end] = " << weights[M-1] << endl; // all pixels for( int j = 0; j < width * height; j++ ) { // all exposures for each pixel float sum = 0.0f; float div = 0.0f; for( int i = 0 ; i < N ; i++ ) { int m = (int) (*imgs[ i ].yi)( j ); float ti = imgs[ i ].ti; sum += weights [ m ] / ti * rcurve [ m ]; div += weights [ m ]; } /* if( div != 0.0f ) (*xj)( j ) = sum / div; else (*xj)( j ) = 0.0f;*/ if( div >= 0.01f ) (*xj)( j ) = sum / div; else (*xj)( j ) = 0; /* { float best_ti = 1e10; int best_v = M-1; for( int i = 0 ; i < N ; i++ ) { int m = (int) (*imgs[ i ].yi)( j ); float ti = imgs[ i ].ti; if( ti < best_ti ) { best_ti = ti; best_v = (int)(*imgs[i].yi)(j); } } (*xj)( j ) = (M-1) / best_ti * rcurve [best_v]; }*/ } return saturated_pixels; } int robertson02_getResponse( pfs::Array2D *xj, const ExposureList &imgs, float *rcurve, const float *weights, int M ) { // number of exposures int N = imgs.size(); // frame size int width = imgs[0].yi -> getCols( ); int height = imgs[0].yi -> getRows( ); // number of saturated pixels int saturated_pixels = 0; // indices int i, j, m; float* rcurve_prev = new float[ M ]; // previous response if( rcurve_prev == NULL ) { std::cerr << "robertson02: could not allocate memory for camera response" << std::endl; exit(1); } // 0. Initialization normalize_rcurve( rcurve, M ); for( m = 0 ; m < M ; m++ ) { // cerr << "m = " << m << " rc = " << rcurve [ m ] << endl; rcurve_prev [ m ] = rcurve [ m ]; } robertson02_applyResponse( xj, imgs, rcurve, weights, M ); // Optimization process bool converged = false; long *cardEm = new long [ M ]; float *sum = new float[ M ]; if( sum == NULL || cardEm == NULL ) { std::cerr << "robertson02: could not allocate memory for optimization process" << std::endl; exit(1); } int cur_it = 0; float pdelta = 0.0f; while( !converged ) { // Display response curve - for debugging purposes /* for( m = 0 ; m < M ; m+=32 ) { cerr << "m = " << m << " rc = " << rcurve [ m ] << endl; }*/ // 1. Minimize with respect to rcurve for( m = 0 ; m < M ; m++ ) { cardEm [ m ] = 0; sum[ m ] = 0.0f; } // For each exposure for( i = 0 ; i < N ; i++ ) { pfs::Array2D* yi = imgs[i].yi; float ti = imgs[ i ].ti; for( j = 0 ; j < width * height ; j++ ) { m = (int) (*yi)( j ); if( m < M && m >= 0 ) { sum[ m ] += ti * (*xj)( j ); cardEm[ m ] ++; } else std::cerr << "robertson02: m out of range: " << m << std::endl; } } for( m = 0; m < M ; m++ ) { if( cardEm[ m ] != 0 ) rcurve [ m ] = sum [ m ] / cardEm [ m ]; else rcurve [ m ] = 0.0f; } // 2. Normalize rcurve float middle_response = normalize_rcurve( rcurve, M ); // 3. Apply new response saturated_pixels = robertson02_applyResponse( xj, imgs, rcurve, weights, M ); // 4. Check stopping condition float delta = 0.0f; int hits = 0; for( m = 0 ; m < M ; m++ ) { if( rcurve[ m ] != 0.0f ) { float diff = rcurve [ m ] - rcurve_prev [ m ]; delta += diff * diff; rcurve_prev [ m ] = rcurve[ m ]; hits++; } } delta /= hits; VERBOSE_STR << " #" << cur_it << " delta=" << delta << " (coverage: " << 100*hits/M << "%)\n"; if( delta < MAX_DELTA ) converged = true; else if( isnan(delta) || cur_it > MAXIT ) { VERBOSE_STR << "algorithm failed to converge, too noisy data in range\n"; break; } pdelta = delta; cur_it++; } if( converged ) VERBOSE_STR << " #" << cur_it << " delta=" << pdelta << " <- converged\n"; delete[] rcurve_prev; delete[] cardEm; delete[] sum; return saturated_pixels; } //---------------------------------------------------------- // private part int comp_floats( const void *a, const void *b ) { return ( (*((float*)a))< (*((float*)b)) ) ; } float normalize_rcurve( float* rcurve, int M ) { int FILTER_SIZE = M / 256; float mean; float rcurve_filt [ M ]; float to_sort [ 2 * FILTER_SIZE + 1 ]; mean = 0.f; for ( int i = 0; i < M; i++ ) { mean += rcurve [ i ]; } mean /= M; if( mean != 0.0f ) for( int m = 0 ; m < M ; m++ ) { rcurve [ m ] /= mean; /* filtered curve - initialization */ rcurve_filt [ m ] = 0.0f; } /* median filter response curve */ for ( int m = FILTER_SIZE ; m < M - FILTER_SIZE; m++ ) { for ( int i = -FILTER_SIZE; i <= FILTER_SIZE; i++ ) to_sort [ i + FILTER_SIZE ] = rcurve[ m + i ]; qsort ( to_sort, 2 * FILTER_SIZE + 1 , sizeof(float), comp_floats ); rcurve_filt [ m ] = to_sort [ FILTER_SIZE ]; } /* boundaries */ for( int m = 0 ; m < FILTER_SIZE ; m++ ) { rcurve_filt [ m ] = rcurve_filt [ FILTER_SIZE ]; rcurve_filt [ M - m - 1 ] = rcurve_filt [ M - FILTER_SIZE - 1 ]; } /* copy curve */ for( int m = 0 ; m < M ; m++ ) { rcurve [ m ] = rcurve_filt [ m ]; } return mean; } /*float normalize_rcurve_old( float* rcurve, int M ) { int Mmin, Mmax; // find min max for( Mmin=0 ; Mmin0 && rcurve[Mmax]==0 ; Mmax-- ); int Mmid = Mmin+(Mmax-Mmin)/2; float mid = rcurve[Mmid]; // std::cerr << "robertson02: middle response, mid=" << mid // << " [" << Mmid << "]" // << " " << Mmin << ".." << Mmax << std::endl; if( mid==0.0f ) { // find first non-zero middle response while( Mmid * */ #include #include #include #include #define NRANSI #include "nrutil.h" #include "mitsunaga99_numerical.h" #define TINY 1.0e-20; void ludcmp(float **a, int n, int *indx, float *d) { int i,imax,j,k; float big,dum,sum,temp; float *vv; vv=vector(1,n); *d=1.0; for (i=1;i<=n;i++) { big=0.0; for (j=1;j<=n;j++) if ((temp=fabs(a[i][j])) > big) big=temp; if (big == 0.0) nrerror("Singular matrix in routine ludcmp"); vv[i]=1.0/big; } for (j=1;j<=n;j++) { for (i=1;i= big) { big=dum; imax=i; } } if (j != imax) { for (k=1;k<=n;k++) { dum=a[imax][k]; a[imax][k]=a[j][k]; a[j][k]=dum; } *d = -(*d); vv[imax]=vv[j]; } indx[j]=imax; if (a[j][j] == 0.0) a[j][j]=TINY; if (j != n) { dum=1.0/(a[j][j]); for (i=j+1;i<=n;i++) a[i][j] *= dum; } } free_vector(vv,1,n); } #undef TINY void lubksb(float **a, int n, int *indx, float b[]) { int i,ii=0,ip,j; float sum; for (i=1;i<=n;i++) { ip=indx[i]; sum=b[ip]; b[ip]=b[i]; if (ii) for (j=ii;j<=i-1;j++) sum -= a[i][j]*b[j]; else if (sum) ii=i; b[i]=sum; } for (i=n;i>=1;i--) { sum=b[i]; for (j=i+1;j<=n;j++) sum -= a[i][j]*b[j]; b[i]=sum/a[i][i]; } } /** Solves the system of linear equations. The result is returned in b[]. * @param n number of equations (equal to number of variables) * @param a[][] coefficients of variables * @param b[] free coefficients */ int MitsunagaNumerical::linearEquationsSystem( int n, float a[][NR_LINEAR_EQUATIONS_MAX], float b[]) { if( n >= NR_LINEAR_EQUATIONS_MAX) { fprintf( stderr, "WARNING: too many equations (nr_solver_linear_equations())\n"); return 1; } float dd; int N = n+1; int *indx = new int[N]; float *bb = new float[N]; // int indx[N]; // float bb[N]; float** aa; aa = (float**)malloc(sizeof(float*) * N); for(int i = 0; i < N; i++) aa[i] = (float*)malloc(sizeof(float) * N); for( int i = 0; i < n; i++) { bb[i+1] = b[i]; for( int j = 0; j < n; j ++) { aa[i+1][j+1] = a[i][j]; } } ludcmp( aa, n, indx, &dd); lubksb( aa, n, indx, bb); for(int i = 0; i < N; i++) if( aa[i] != NULL) free(aa[i]); if( aa != NULL) free(aa); for( int i = 0; i < n; i++) { b[i] = bb[i+1]; } delete[] indx; delete[] bb; return 0; } pfstools-2.2.0/src/camera/pfsalign.cpp0000664000701400070140000006366614105165614016430 0ustar rkm38rkm38/** * @file pfsalign.cpp * @brief Image stack alignment using feature matching * * The code relies on OpenCV SURF feature detector / descriptor. The ideas for * prunning false matches are mostly based on the book: * * Lagani, R. (2011). OpenCV 2 Computer Vision Application Programming Cookbook. * Packt Publishing. * * * This file is a part of pfscalibration package. * ---------------------------------------------------------------------- * Copyright (C) 2003-2013 Rafal Mantiuk and Grzegorz Krawczyk * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Rafal Mantiuk, * * $Id: pfsalign.cpp,v 1.11 2013/11/14 21:28:28 rafm Exp $ */ #include #include #include #include #include #include // Because non-free OpenCV modules is not included in most of the distributions // the current codes relies now on a less restrictive AKAZE feature detector // Uncomment the line below to use SURF //#define USE_SURF #include #include #include #include #include #include #ifdef USE_SURF #include #endif #include #define PROG_NAME "pfsalign" #define VERBOSE_STR if( verbose ) std::cerr << PROG_NAME ": " bool verbose = false; class QuietException { }; using namespace cv; using namespace std; void printHelp() { fprintf( stderr, PROG_NAME " [-r index|--reference index] [-c (min|max)|--crop (min|max)] [-d|--display-matches] [-f|--fail-no-match] -v -h\n" "See man page for more information.\n" ); } class Toc { double tickCount; bool newLine; const char *name; public: void tic( const char *name = NULL, bool newLine = false ) { this->name = name; this->newLine = newLine; if( name != NULL ) name = "processing"; VERBOSE_STR << "Starting " << name << "..."; if( newLine ) VERBOSE_STR << std::endl; tickCount = static_cast( getTickCount() ); } void toc( ) { double duration = (static_cast( getTickCount() ) - tickCount) / getTickFrequency(); if( newLine ) { VERBOSE_STR << "Completed " << name << ". It took " << duration << " seconds." << std::endl; } else VERBOSE_STR << "completed. It took " << duration << " seconds." << std::endl; } }; void pruneNNDR( std::vector > &matches, float ratio ) { for( vector >::iterator it = matches.begin(); it != matches.end(); it++ ) { bool prune = false; if( it->size() < 2 ) prune = true; else { float NNDR = (*it)[0].distance / (*it)[1].distance; if( NNDR >= ratio ) prune = true; } if( prune ) it->clear(); } } void symmetryTest( const vector > &matches_12, const vector > &matches_21, vector &matches_sym ) { for( vector< vector >::const_iterator it1 = matches_12.begin(); it1 != matches_12.end(); it1++ ) { if( it1->size() == 0 ) continue; for( vector< vector >::const_iterator it2 = matches_21.begin(); it2 != matches_21.end(); it2++ ) { if( it2->size() == 0 ) continue; if( (*it1)[0].queryIdx == (*it2)[0].trainIdx && (*it2)[0].trainIdx == (*it1)[0].queryIdx ) { matches_sym.push_back( DMatch( (*it1)[0].queryIdx, (*it1)[0].trainIdx, (*it1)[0].distance )); break; } } } } /** Match feature points in a pair image and find homography. */ bool alignImagePair( const Mat &ref_img, const Mat &exp_img, Mat &homography, int sensitivity, bool display_matches ) { Toc toc; Toc toc_global; toc_global.tic( "alignment of image pair", true ); homography = Mat::eye( 3, 3, CV_64F ); // cv::imshow( "Result", ref_img ); // cv::imshow( "Result2", exp_img ); // cv::waitKey(0); // Ptr detector; // detector = new GoodFeaturesToTrackDetector(); // detector = new SurfFeatureDetector(); std::vector keypoints1, keypoints2; Mat descr_ref, descr_exp; #ifdef USE_SURF Ptr detector(new DynamicAdaptedFeatureDetector( new SurfAdjuster( (11-sensitivity) * 100.f, 2, 1000 ), 100, 1000, sensitivity/2+2 )); toc.tic( "feature detection" ); detector->detect( ref_img, keypoints1 ); detector->detect( exp_img, keypoints2 ); toc.toc( ); #else Ptr akaze = AKAZE::create(); toc.tic( "feature detection and extraction" ); akaze->setThreshold( 0.005f ); akaze->detectAndCompute( ref_img, noArray(), keypoints1, descr_ref); akaze->detectAndCompute( exp_img, noArray(), keypoints2, descr_exp); toc.toc( ); VERBOSE_STR << "Detected " << keypoints1.size() << " keypoints in the reference image." << endl; VERBOSE_STR << "Detected " << keypoints2.size() << " keypoints in the test image." << endl; #endif if( keypoints1.size() < 10 || keypoints2.size() < 10 ) { cerr << PROG_NAME ": Could not detect a sufficient number of keypoints (found " << keypoints1.size() << " and " << keypoints2.size() << " keypoints)" << endl; if( display_matches ) { Mat vis; vector inliners_c; std::vector matches_sym; drawMatches( ref_img, keypoints1, exp_img, keypoints2, matches_sym, vis, Scalar(0,0,255), Scalar(0,255,255), inliners_c, DrawMatchesFlags::DRAW_RICH_KEYPOINTS ); cv::namedWindow( "Image pair matches" ); cv::imshow( "Image pair matches", vis ); cv::waitKey(0); } return false; } // SiftDescriptorExtractor surfDesc; #ifdef USE_SURF SurfDescriptorExtractor surfDesc; toc.tic( "descriptor extraction" ); surfDesc.compute( ref_img, keypoints1, descr_ref ); surfDesc.compute( exp_img, keypoints2, descr_exp ); toc.toc( ); #endif #ifdef USE_SURF FlannBasedMatcher matcher; // BruteForceMatcher< cv::L2 > matcher; #else BFMatcher matcher(NORM_HAMMING); #endif toc.tic( "matching" ); std::vector< std::vector > matches_rt; std::vector< std::vector > matches_tr; matcher.knnMatch( descr_ref, descr_exp, matches_rt, 2 ); matcher.knnMatch( descr_exp, descr_ref, matches_tr, 2 ); pruneNNDR( matches_rt, 1 ); pruneNNDR( matches_tr, 1 ); std::vector matches_sym; symmetryTest( matches_rt, matches_tr, matches_sym ); toc.toc( ); std::vector::iterator it; vector p1, p2; Mat_ pp1(matches_sym.size(),2); Mat_ pp2(matches_sym.size(),2); int kk = 0; for( it = matches_sym.begin(); it != matches_sym.end(); it++, kk++ ) { const Point2f from = keypoints1[it->queryIdx].pt; p1.push_back( Point3f( from.x, from.y, 1 ) ); pp1(kk,0) = from.x; pp1(kk,1) = from.y; // pp1(kk,2) = 1; const Point2f to = keypoints2[it->trainIdx].pt; p2.push_back( Point3f( to.x, to.y, 1 ) ); pp2(kk,0) = to.x; pp2(kk,1) = to.y; //pp2(kk,2) = 1; // std::cout << "Matches: " << from << " -> " << to << std::endl; } if( matches_sym.size() < 9 ) { cerr << PROG_NAME ": Not enough matches found between a pair of images (found " << matches_sym.size() << ")" << endl; return false; } // Mat affine = estimateRigidTransform( e1, e2, false ); vector inliners(matches_sym.size(), 0); // affine = findHomography( pp1, pp2, inliners, CV_RANSAC, 1. ); homography = findHomography( pp2, pp1, RANSAC, 1., inliners ); // solve( pp1, pp2, affine, DECOMP_SVD ); // Mat affine = (Mat_(2,3) << 1, 0, 0, 0, 1, 10); int inliner_count = count( inliners.begin(), inliners.end(), 1 ); VERBOSE_STR << "Found " << matches_sym.size() << " matching features, reduced to " << inliner_count << " inliners." << endl; if( display_matches ) { Mat vis; vector inliners_c(matches_sym.size(), 0); for( size_t i = 0; i < inliners.size(); i++ ) inliners_c[i] = (char)inliners[i]; drawMatches( ref_img, keypoints1, exp_img, keypoints2, matches_sym, vis, Scalar(0,0,255), Scalar(0,255,255), inliners_c, DrawMatchesFlags::DRAW_RICH_KEYPOINTS|DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS ); cv::namedWindow( "Image pair matches" ); cv::imshow( "Image pair matches", vis ); cv::waitKey(0); } if( inliner_count < 9 ) { homography = Mat::eye( 3, 3, CV_64F ); cerr << PROG_NAME ": Not enough inliners to find a reliable transformation." << endl; return false; } toc_global.toc(); return true; } /** Convert floating point image to 8-bit image. */ Mat convert_to_8bit( Mat &in, bool apply_gamma ) { Mat out; if( !apply_gamma ) { in.convertTo( out, CV_8UC3, 255 ); } else { out.create( in.rows, in.cols, CV_8UC3 ); for(int i = 0; i < in.rows; i++) { Vec3f *i_p = in.ptr(i); Vec3b *o_p = out.ptr(i); for(int j = 0; j < in.cols; j++) { for( int cc=0; cc < 3; cc++ ) { o_p[j][cc] = saturate_cast(powf( i_p[j][cc], 1.f/2.2f ) * 255.f); } } } } return out; } // Find maximum and positive minimum for an image void find_min_max( Mat &in, float &minV, float &maxV ) { minV = 1e10f; maxV = -1e10f; for(int i = 0; i < in.rows; i++) { Vec3f *i_p = in.ptr(i); for(int j = 0; j < in.cols; j++) { for( int cc=0; cc < 3; cc++ ) { float v = i_p[j][cc]; if( v > 0 ) { if( v < minV ) minV = v; if( v > maxV ) maxV = v; } } } } } Mat convert_and_log_scale( Mat &in, float min, float max, float mult, float gamma ) { Mat out; out.create( in.rows, in.cols, CV_8UC3 ); const float l_min = logf( min ); const float l_max = logf( max ); const float l_mult = logf( mult ); for(int i = 0; i < in.rows; i++) { Vec3f *i_p = in.ptr(i); Vec3b *o_p = out.ptr(i); for(int j = 0; j < in.cols; j++) { for( int cc=0; cc < 3; cc++ ) { if( i_p[j][cc] > 0.f ) o_p[j][cc] = saturate_cast( ((gamma*(logf(i_p[j][cc]) - l_max) + l_mult) / (l_max-l_min) + 1.f) * 255.f); else o_p[j][cc] = 0; } } } return out; } /** Convert a pair of floating point images to 8-bit. */ void convert_to_8bit( Mat &in1, Mat &in2, Mat &out1, Mat &out2, float exp_mult1, float exp_mult2, bool is_ldr ) { if( is_ldr && exp_mult1 == 1.f && exp_mult2 == 1.f ) { in1.convertTo( out1, CV_8UC3, 255*exp_mult1); in2.convertTo( out2, CV_8UC3, 255*exp_mult2); } else { float minV1, minV2, maxV1, maxV2; find_min_max( in1, minV1, maxV1 ); find_min_max( in2, minV2, maxV2 ); minV1 *= exp_mult1; maxV1 *= exp_mult1; minV2 *= exp_mult2; maxV2 *= exp_mult2; float gamma = is_ldr ? 2.2f : 1.f; minV1 = powf( minV1, gamma ); minV2 = powf( minV2, gamma ); float minV, maxV; minV = std::min( minV1, minV2 ); maxV = std::max( maxV1, maxV2 ); if( (maxV/minV) > 10000.f ) { // To avoid compressing contrast too much minV = maxV / 10000.f; } if( (maxV/minV) < 100.f ) { // To avoid streching contrast too much minV = maxV / 100.f; } VERBOSE_STR << "Using log scaling (min val: " << minV1 << " max value: " << maxV1 << ")" << endl; out1 = convert_and_log_scale( in1, minV, maxV1, exp_mult1, gamma ); out2 = convert_and_log_scale( in2, minV, maxV1, exp_mult2, gamma ); } } Point2f intersect_lines( Point2f p1, Point2f p2, Point2f p3, Point2f p4 ) { float s = ((p4.x-p3.x)*(p3.y-p1.y) - (p3.x-p1.x)*(p4.y-p3.y)) / ( (p4.x-p3.x)*(p2.y-p1.y) - (p2.x-p1.x)*(p4.y-p3.y) ); Point2f p; p.x = p1.x + s * (p2.x-p1.x); p.y = p1.y + s * (p2.y-p1.y); return p; } Rect auto_crop( const Size2f &size, const vector &rotated ) { assert( rotated.size() == 4 ); // 4 corners of the polygon Point2f centre; for( int kk=0; kk < 4; kk++ ) centre += rotated[kk]; centre = centre * 0.25; //cout << "Float: " << (rot_mat.type() == CV_32F) << endl; float best_d = 1e10; Point2d best_p; for( int jj = 0; jj < 2; jj++ ) { for( int ii=0; ii < 2; ii++ ) { Point2f to; if( jj == 0 ) to = centre - Point2f( size.width/2, size.height/2 ); else to = centre - Point2f( -size.width/2, size.height/2 ); Point2f p1 = intersect_lines( centre, to, rotated[ii], rotated[ii+1] ); float d = powf(p1.x-centre.x,2)+powf(p1.y-centre.y,2); if( d < best_d ) { best_d = d; best_p = p1; } } } Point2f ul( centre.x - fabs(best_p.x-centre.x), centre.y - fabs(best_p.y-centre.y) ); Point2f br( centre.x + fabs(best_p.x-centre.x), centre.y + fabs(best_p.y-centre.y) ); Rect crop( ul, br ); return crop; } struct FrameInfo { Mat image; std::string file_name; pfs::Frame *frame; Size size; FrameInfo( Mat &image, const char *file_name, pfs::Frame *frame ) : image( image ), file_name( file_name ), frame( frame ) { size = Size( image.cols, image.rows ); } }; void alignFrames(int argc, char *argv[]) { pfs::DOMIO pfsio; int reference_index = 0; // Index of the reference image bool display_matches = false; bool crop_min = false; bool fail_on_no_match = false; int sensitivity = 5; static struct option cmdLineOptions[] = { { "help", no_argument, NULL, 'h' }, { "verbose", no_argument, NULL, 'v' }, { "reference", required_argument, NULL, 'r' }, { "crop", required_argument, NULL, 'c' }, { "sensitivity", required_argument, NULL, 's' }, { "fail-no-match", no_argument, NULL, 'f' }, { "display-matches", no_argument, NULL, 'd' }, { NULL, 0, NULL, 0 } }; int optionIndex = 0; while( 1 ) { int c = getopt_long (argc, argv, "r:c:s:fhvd", cmdLineOptions, &optionIndex); if( c == -1 ) break; switch( c ) { case 'h': printHelp(); throw QuietException(); case 'v': verbose = true; break; case 'r': reference_index = strtol( optarg, NULL, 10 ) - 1; break; case 'd': display_matches = true; break; case 'c': if( !strcasecmp( optarg, "min" ) ) { crop_min = true; } else if( !strcasecmp( optarg, "max" ) ) { crop_min = false; } else throw pfs::Exception( "Unrecognized cropping mode" ); break; case 'f': fail_on_no_match = true; break; case 's': sensitivity = strtol( optarg, NULL, 10 ); if( sensitivity <= 0 || sensitivity > 10 ) { throw pfs::Exception( "Sensitivity parameter must be within 1-10 range."); } break; case '?': throw QuietException(); case ':': throw QuietException(); } } vector frames; if( crop_min ) { VERBOSE_STR << "Cropping to the size that contains only overlaping pixels (min)" << endl; } else { VERBOSE_STR << "Cropping to the size that contains all pixels (max)" << endl; } // Load all images // bool first_frame = true; int frame_count = 0; for( ; true; frame_count++ ) { pfs::Frame *frame = pfsio.readFrame( stdin ); if( frame == NULL ) break; // No more frames const char *f_name = frame->getTags()->getString("FILE_NAME"); if( f_name == NULL ) { std::stringstream f_name_str; f_name_str << "frame_" << frame_count; f_name = f_name_str.str().c_str(); } VERBOSE_STR << "Loading " << f_name << endl; pfs::Channel *X, *Y, *Z; frame->getXYZChannels( X, Y, Z ); if( X != NULL ) { // Color, XYZ pfs::Channel* &R = X; pfs::Channel* &G = Y; pfs::Channel* &B = Z; pfs::transformColorSpace( pfs::CS_XYZ, X, Y, Z, pfs::CS_RGB, R, G, B ); Mat img; img.create( frame->getHeight(), frame->getWidth(), CV_32FC3 ); int in_index = 0; for(int i = 0; i < img.rows; i++) { Vec3f *o_p = img.ptr(i); for(int j = 0; j < img.cols; j++) { o_p[j][0] = (*B)(in_index); o_p[j][1] = (*G)(in_index); o_p[j][2] = (*R)(in_index); in_index++; } } frames.push_back( FrameInfo( img, f_name, frame ) ); } else throw pfs::Exception( "Only color images are supported" ); // Remove all channels from the frame to save memory pfs::ChannelIteratorPtr cit( frame->getChannelIterator() ); while( cit->hasNext() ) { pfs::Channel *ch = cit->getNext(); frame->removeChannel( ch ); } } if( reference_index < 0 || (size_t)reference_index >= frames.size() ) throw pfs::Exception( "Reference image index out of range" ); vector homoM; vector imageSizes; for( int ff=1; ff < (int)frames.size(); ff++ ) { VERBOSE_STR << "Processing " << frames[ff-1].file_name << " and " << frames[ff].file_name << endl; cv::Mat ref_img = frames[ff-1].image; cv::Mat exp_img = frames[ff].image; imageSizes.push_back( exp_img.size() ); bool is_ldr = false; const char *lum_type = frames[ff].frame->getTags()->getString("LUMINANCE"); if( lum_type ) { if( !strcmp( lum_type, "DISPLAY" ) ) is_ldr = true; } // Extract exposure values float exp1 = 1.f, exp2 = 1.f; const char* exp_str = frames[ff-1].frame->getTags()->getString("BV"); if( exp_str != NULL ) exp1 = powf(2.0f,strtof( exp_str, NULL )); exp_str = frames[ff].frame->getTags()->getString("BV"); if( exp_str != NULL ) exp2 = powf(2.0f,strtof( exp_str, NULL )); if( exp1 / exp2 != 1.f ) { VERBOSE_STR << "Exposure pair: " << exp1 << ", " << exp2 << " (" << exp2/exp1 << " ratio)" << endl; } const float exp_mult1 = exp1 / std::min( exp1, exp2 ); const float exp_mult2 = exp2 / std::min( exp1, exp2 ); Mat ref_img_8b, exp_img_8b; // Mat ref_img_8b = convert_to_8bit( ref_img, apply_gamma ); // Mat exp_img_8b = convert_to_8bit( exp_img, apply_gamma ); // convert_to_8bit( ref_img, exp_img, ref_img_8b, exp_img_8b, 1, 1, is_ldr ); convert_to_8bit( ref_img, exp_img, ref_img_8b, exp_img_8b, exp_mult1, exp_mult2, is_ldr ); Mat homography; bool success = alignImagePair( ref_img_8b, exp_img_8b, homography, sensitivity, display_matches ); if( !success && fail_on_no_match ) throw pfs::Exception( "Stopping because could not find a match between image pair"); VERBOSE_STR << "Homography (for the image pair):" << endl << homography << endl; homoM.push_back( homography ); } // Chain all transformations and find the cropping box vector homoMC; Rect_ pano_rect( 0, 0, frames[0].image.cols, frames[0].image.rows ); for( int kk = 0; kk < (int)frames.size(); kk++ ) { Mat_ trans = Mat::eye(3,3,CV_64F); for( int ll = min( kk, reference_index )+1; ll <= max( kk, reference_index); ll++ ) { if( ll > 0 ) trans = trans*homoM[ll-1]; } if( kk < reference_index ) trans = trans.inv(); homoMC.push_back( trans ); double data[4][3] = { { 0., 0., 1. }, { (double)frames[kk].size.width, 0., 1. }, { (double)frames[kk].size.width, (double)frames[kk].size.height, 1. }, { 0., (double)frames[kk].size.height, 1. } }; Mat corners( 4, 3, CV_64F, data ); Mat corners_trans = trans * corners.t(); // VERBOSE_STR << "Image: " << fileNames[kk] << endl; // VERBOSE_STR << " Corners: " << trans * corners.t() << endl; Mat p_nh; //( 4, 3, CV_32F ); Mat_ corners_f; corners_trans.convertTo( corners_f, CV_32F ); convertPointsFromHomogeneous( corners_f.t(), p_nh ); if( crop_min ) { vector dest_rect(4); for( int ii=0; ii < 4; ii++ ) { dest_rect[ii].x = p_nh.at(ii,0); dest_rect[ii].y = p_nh.at(ii,1); } Rect_ img_rect = auto_crop( frames[kk].size, dest_rect ); Rect_ img_rect_d = img_rect; pano_rect = pano_rect & img_rect_d; } else { Rect_ img_rect = boundingRect( p_nh ); pano_rect = pano_rect | img_rect; } // VERBOSE_STR << "Bounding rect: " << pano_rect.x << ", " << pano_rect.y << " - " << pano_rect.width << "x" << pano_rect.height << endl; } // Align Size dest_size = Size( ceil( pano_rect.width ), ceil( pano_rect.height) ); // Mat pano_img( ceil( pano_rect.height), ceil( pano_rect.width ), CV_8UC3 ); // pano_img.setTo( Vec3b(255, 255, 255 )); for( size_t ff=0; ff < frames.size(); ff++ ) { VERBOSE_STR << "Warping: "<< frames[ff].file_name << endl; Mat exp_img = frames[ff].image; Mat_ translate = Mat::eye(3,3,CV_64F); translate(0,2) = -pano_rect.x; translate(1,2) = -pano_rect.y; Mat trans = translate*homoMC[ff]; VERBOSE_STR << "Homography (to reference): " << trans << endl; Mat res_img; warpPerspective( exp_img, res_img, trans, dest_size, INTER_LINEAR ); pfs::Frame *alignedFrame = NULL; alignedFrame = pfsio.createFrame( res_img.cols, res_img.rows ); pfs::Channel *X = alignedFrame->createChannel( "X" ); pfs::Channel *Y = alignedFrame->createChannel( "Y" ); pfs::Channel *Z = alignedFrame->createChannel( "Z" ); pfs::Channel* &R = X; pfs::Channel* &G = Y; pfs::Channel* &B = Z; int out_index = 0; for(int i = 0; i < res_img.rows; i++) { Vec3f *i_p = res_img.ptr(i); for(int j = 0; j < res_img.cols; j++) { (*B)(out_index) = i_p[j][0]; (*G)(out_index) = i_p[j][1]; (*R)(out_index) = i_p[j][2]; out_index++; } } pfs::transformColorSpace( pfs::CS_RGB, R, G, B, pfs::CS_XYZ, X, Y, Z ); pfs::copyTags( frames[ff].frame, alignedFrame ); pfsio.writeFrame( alignedFrame, stdout ); pfsio.freeFrame( alignedFrame ); pfsio.freeFrame( frames[ff].frame ); // TODO: Add alpha channel /* Mat in_mask( exp_img.size(), CV_8U ), out_mask; in_mask.setTo( Scalar( 255 ) ); warpPerspective( in_mask, out_mask, trans, pano_img.size(), INTER_LINEAR ); res_img.copyTo( pano_img, out_mask ); double data[4][3] = { { 0, 0, 1 }, { exp_img.cols, 0, 1 }, { exp_img.cols, exp_img.rows, 1 }, { 0, exp_img.rows, 1 } }; Mat corners( 4, 3, CV_64F, data ); Mat_ corners_trans = trans * corners.t(); Mat p_nh; //( 4, 3, CV_32F ); Mat_ corners_f; corners_trans.convertTo( corners_f, CV_32F ); convertPointsFromHomogeneous( corners_f.t(), p_nh );*/ // Scalar borderColor = CV_RGB( 0, 0, 0 ); // line( pano_img, Point2f( p_nh(0,0), p_nh(0,1) ), Point2f( p_nh(1,0), p_nh(1,1) ), borderColor, 3 ); // line( pano_img, Point2f( p_nh(1,0), p_nh(1,1) ), Point2f( p_nh(2,0), p_nh(2,1) ), borderColor, 3 ); // line( pano_img, Point2f( p_nh(2,0), p_nh(2,1) ), Point2f( p_nh(3,0), p_nh(3,1) ), borderColor, 3 ); // line( pano_img, Point2f( p_nh(3,0), p_nh(3,1) ), Point2f( p_nh(0,0), p_nh(0,1) ), borderColor, 3 ); } } int main( int argc, char* argv[] ) { try { alignFrames( argc, argv ); } catch( pfs::Exception ex ) { fprintf( stderr, PROG_NAME " error: %s\n", ex.getMessage() ); return EXIT_FAILURE; } catch( QuietException ex ) { return EXIT_FAILURE; } return EXIT_SUCCESS; } pfstools-2.2.0/src/camera/pfs_automerge0000664000701400070140000000471314105165614016670 0ustar rkm38rkm38#!/bin/bash # Batch processing of large collections of HDR exposures # Merge RAW files from individual directories (creates with pfs_split_exposures) into HDR images if test -z "$1" || test "$1" = "--help"; then cat <] [--format (exr|hdr)] See the man page for more information. EOF exit 1 fi do_align=false DESTDIR=merged METHOD=robertson FORMAT=exr #Arguments used for all images passed to pfsout if test -n "$1"; then while test "${1:0:1}" = "-"; do if [ "$1" = "-a" ] || [ "$1" = "--align" ]; then do_align=true elif [ "$1" = "-d" ] || [ "$1" = "--dest" ]; then shift DESTDIR=$1 elif [ "$1" = "-f" ] || [ "$1" = "--format" ]; then shift FORMAT=$1 if [ "$FORMAT" != "exr" ] && [ "$FORMAT" != "hdr" ]; then echo "Unrecognized format '$1'. Must be 'exr' or 'hdr'" exit 1 fi else echo "Unrecognized option $1" exit 1 fi shift done fi if $do_align; then echo "Images will be aligned before merging" fi SRCDIR=$1 mkdir -p $DESTDIR for dir in `find $SRCDIR -mindepth 1 -type d`; do base_name=`basename "$dir"` echo "Processing $base_name" EXT="" # test -n "`find $dir -name *.jpg`" && EXT="jpg" # test -n "`find $dir -name *.JPG`" && EXT="JPG" test -n "`find $dir -name '*.ARW'`" && EXT="ARW" test -n "`find $dir -name '*.DNG'`" && EXT="DNG" test -n "`find $dir -name '*.CR2'`" && EXT="CR2" test -n "`find $dir -name '*.cr2'`" && EXT="cr2" if test -z "$EXT"; then echo "No recognized images found, skipping the directory" else echo " extension: $EXT" DEST_FILE="${DESTDIR}/${base_name}.${FORMAT}" if [ -f ${DEST_FILE} ]; then echo " destination file exist, merging skipped" else echo " merging $DEST_FILE" if $do_align; then pfsinme $dir/*.$EXT | pfsalign -v -c min | pfshdrcalibrate -d -c $METHOD -r linear --bpp 16 --verbose | pfsout $DEST_FILE else pfsinme $dir/*.$EXT | pfshdrcalibrate -c $METHOD -r linear --bpp 16 --verbose | pfsout $DEST_FILE fi fi fi done pfstools-2.2.0/src/camera/pfshdrcalibrate.10000664000701400070140000001744314105165614017330 0ustar rkm38rkm38.TH "pfshdrcalibrate" 1 .SH NAME pfshdrcalibrate \- Create an HDR image or calibrate a response curve from a set of differently exposed images supplied in PFS stream. .SH SYNOPSIS .B pfshdrcalibrate [--response ] [--calibration ] [--gauss ] [--response-file ] [--save-response ] [--multiplier ] [--bpp ] [--luminance] [--samples ] [--saturation-offset ] [--deghosting ] [--noise-parameters ] [--help] [--verbose] .SH DESCRIPTION Create an HDR image or calibrate a response curve from a set of differently exposed images supplied in PFS stream. When used with 8bit images, luminance in the output HDR image corresponds to real world values in [cd/m^2] provided that hdrgen script contained correct information on exposure time, aperture and iso speed. Note that sometimes ISO speed indicated by camera does not correspond to standard (ISO-100 is in fact ISO-125). The accuracy of absolute calibration has not been thoroughly tested with different camera models, however one can expect the relative measurement error below 8%. Use pfsabsolute in case of systematic error. The merging of multiple exposures is done in a noise-optimal manner, as explained in the paper: .IP Hanji, Param, Fangcheng Zhong, and Rafal K. Mantiuk. .PD 0 .IP “Noise-Aware Merging of High Dynamic Range Image Stacks without Camera Calibration.†.IP In Advances in Image Manipulation (ECCV Workshop), 2020. http://arxiv.org/abs/2009.07975. .SH OPTIONS .TP --response , -r Allows one to choose from predefined response curves. This can be used either to apply this response or use it as an initialization for automatic self-calibration. Predefined response curves are: "linear", "gamma", "log". Default is "linear". This option can be used only with Robertson method. .TP --calibration , -c Type of automatic self-calibration method used for recovery of the response curve and/or type of method used for HDR merging. Accepted types include: "robertson", "mitsunaga". "robertson" is the default and recommended algorithm (see commends in the Bugs section below). More infomation on the algorithms can be found in: .IP M.A. Robertson, S. Borman and R.L. Stevenson .PD 0 .IP Dynamic range improvement through multiple exposures .IP In: Proc. of International Conference on Image Processing 1999 (ICIP 99), pp 159-163 vol.3 .PD .IP and .IP T. Mitsunaga and S. K. Nayar .PD 0 .IP Radiometric Self Calibration .IP In: Proc on IEEE Conf. on Computer Vision and Pattern Recognition (CVPR'99). Volume 1, p. 1374 .TP --gauss , -g Sigma value for the Gaussian used as a weighting function (in the range 0-1). Applies to Robertson02 algorithm. Default value: 0.2 .TP --response-file , -f Use response curve saved in the matlab format file. Turns off automatic self-calibration. Uses Robertson02 or Mitsunaga99 model to apply the response curve (see -c option). .TP --save-response , -s Saves the response curve calculated during automatic self-calibration stage in a matlab format file. Can be later reused for set of images captured with given camera. Also works fine for plotting with gnuplot. .TP --multiplier , -m Input multiplier value. Can be used to manipulate the range of source exposures. Default value for Robertson method is 256 since LDR images are by default scaled to 0..1. This value is set to 1.0 for Mitsunaga method. .TP --bpp , -b Number of bits per pixel in input data from the camera. Default value is 8. .TP --samples , -p Number of samples used during the self-calibration in Mitsunaga algorithm. Default is 50000. .TP --saturation-offset , -o Pixels are considered saturated if their value exceeds max_val * (1-sat_offset), where max_val is the maximum pixel value (e.g. 255 or 65535) and sat_offset is the percentage of the maximum value. The default value is 0.01. Use this if you can see banding or wrong colors in the regions in which one of the exposures is saturated. .TP --noise-parameters , -n Measured noise parameters for the specific camera used. This can be specified in 2 ways. The first is using a comma separated list of 5 values (3 color coefficients, std_readout and std_adc). Alternatively, some presets have been defined and can be used by specifying the camera name. Cameras currently supported: SonyA7r1, CanonT1, SonyA7r3 .TP --deghosting, -d [] EXPERIMENTAL. Try to use a simple rejection criterion to reduce ghosting artifacts due to movement. If the option is specified, the pixel values that standard deviations of the noise away from the reference exposure are rejected. When the option is missing from the argument list, the deghosting is disabled. When the option is specified without any value, 2 standard deviations are used. .TP --luminance, -Y Recovery of response curve will be performed for luminance channel only. .TP --verbose Print additional information during program execution. .TP --help Print list of command line options. .SH EXAMPLES .TP pfsinme *.JPG | pfshdrcalibrate -v -s response.m | pfsview .IP Recover the response curve from set of all JPEG files in the current directory and save it to response.m file. To view the response curve, use pfsplotresponse command. .TP pfsinme *.CR2 | pfssize --maxx 1200 | pfshdrcalibrate -r linear -v --bpp 16 | pfsout result.exr .IP Read Camera RAW images (from Canon), resize them so that the image width is equal or less 1200 pixels, merge them into an HDR image using all 16 bits and save as an Open EXR image. .TP pfsinhdrgen sample.hdrgen | pfshdrcalibrate -x -f response.m | pfsview .IP Create an HDR image from exposures defined in sample.hdrgen using the response curve "response.m" and view it. Fix the problem with black values given to overexposed pixels. .TP pfsinhdrgen sample.hdrgen | pfshdrcalibrate | pfsview .IP Create an HDR image from exposures defined in sample.hdrgen using the default self-calibration method and view it. .TP pfsinhdrgen sample_dcraw.hdrgen | pfshdrcalibrate -b 16 -r linear -c none | pfsview .IP Given that the script sample_dcraw.hdrgen refers to camera RAW files (see pfsindcraw), this example will generate an HDR image assuming a linear response. .TP pfsinhdrgen sample.hdrgen | pfshdrcalibrate | pfsview .IP Create an HDR image from exposures defined in sample.hdrgen using the default self-calibration method and view it. .TP pfsinhdrgen sample.hdrgen | pfshdrcalibrate -c mitsunaga -samples 100000 -s resp_mitsunaga.m >/dev/null .IP Create an HDR image from exposures defined in sample.hdrgen using the mitsunaga self-calibration method with 100000 samples and save it to "resp_mitsunaga.m". .SH "SEE ALSO" .BR pfsplotresponse (1) .BR pfsinhdrgen (1) .BR jpeg2hdrgen (1) .BR pfsview (1) .BR pfsindcraw (1) .BR pfsabsolute (1) .BR pfsglview (1) .SH BUGS Currently Mitsunaga and Nayar's method does not produce reliable camera response curves. Robertson's method should be used instead. .PP Pink pixels may appear in saturated areas of outdated version of DCRAW is used. It is recommended to install libraw instead of DCRAW (which is discontinued). .PP Robertson's method may produce banding or wrong colors in saturated areas. For best results, there should be a sufficient numbers of exposures in which no pixels is over- or under-saturated in all the exposures. It is possible to fix these issues with some heuristics (as most HDR merging software does), but it is currently not done in this release. pfscalibration was meant to be used for research purposes and getting accurate result is more important than generating good looking images. The heuristics could hide the well visible artifacts, but would also introduce error to the measurements. .PP For any other issues please report bugs and comments on implementation to the discussion group http://groups.google.com/group/pfstools pfstools-2.2.0/src/camera/pfshdrcalibrate.cpp0000664000701400070140000007451014105165614017750 0ustar rkm38rkm38/** * @brief Create an HDR image or calibrate a response curve from a set * of differently exposed images supplied in PFS stream * * * This file is a part of PFS CALIBRATION package. * ---------------------------------------------------------------------- * Copyright (C) 2004 Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Grzegorz Krawczyk, * @author Ivo Ihrke, * * $Id: pfshdrcalibrate.cpp,v 1.16 2011/02/24 17:35:59 ihrke Exp $ */ #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; #define PROG_NAME "pfshdrcalibrate" inline float max3( float a, float b, float c ) { float max = (a>b) ? a : b; return (c>max) ? c : max; } inline float min3( float a, float b, float c ) { // ignore zero values if( int(a)==0 ) a=1e8; if( int(b)==0 ) b=1e8; if( int(c)==0 ) c=1e8; float min = (a] [--luminance]\n" "\t[--response ] [--response-file ] \n" "\t[--save-response ] \n" "\t[--multiplier ] \n" "\t[--bpp ] \n" "\t[--saturation-offset ] \n" "\t[--deghosting ] \n" "\t[--noise-parameters ] \n" "\t[--verbose] [--help]\n" "See man page for more information.\n" ); } void pfshdrcalibrate( int argc, char* argv[] ) { /* ------------------------------------------------------------------------------------------------ */ /* -------------------------------- initialization start ------------------------------------------ */ /* ------------------------------------------------------------------------------------------------ */ pfs::DOMIO pfsio; enum TCalibration { NONE, CALIBRATE } opt_calibration = CALIBRATE; enum TResponse { FROM_FILE, LINEAR, GAMMA, LOG10 } opt_response = LINEAR; enum TMethod { ROBERTSON_METHOD, MITSUNAGA_METHOD } opt_method = ROBERTSON_METHOD; noise_parameters camera_presets[] = { { "Empty", {1, 1, 1}, 0, 0 }, { "SonyA7r1", {1.308256, 1.321364, 1.28384}, 2.796408, 0.1434412 }, { "CanonT1", {5.45232, 4.73024, 4.61276}, 3.752112, 20.021 }, { "SonyA7r3", {1.688176, 1.537108, 1.555868}, 2.820508, 12.1138 } }; /* defaults */ float input_multiplier = 1.0f; FILE *responseFile = NULL; FILE *responseSaveFile = NULL; int opt_bpp = 8; bool opt_fillgaps = false; /* todo remove */ bool opt_luminance = false; float opt_gauss = 0.2; int opt_maxresponse = -1; int opt_minresponse = -1; unsigned long mitsunaga_sample_no = MITSUNAGA_SAMPLES_NO; int opt_saturation_offset = -1; float opt_saturation_offset_perc = -1; float opt_deghosting = -1; noise_parameters *camera = &camera_presets[0]; /* helper */ int c; static struct option cmdLineOptions[] = { { "help", no_argument, NULL, 'h' }, { "verbose", no_argument, NULL, 'v' }, { "luminance", no_argument, NULL, 'Y' }, { "method", required_argument, NULL, 'c' }, { "gauss", required_argument, NULL, 'g' }, { "max-response", required_argument, NULL, 'A' }, { "min-response", required_argument, NULL, 'S' }, { "response", required_argument, NULL, 'r' }, { "response-file", required_argument, NULL, 'f' }, { "save-response", required_argument, NULL, 's' }, { "multiplier", required_argument, NULL, 'm' }, { "bpp", required_argument, NULL, 'b' }, { "samples", required_argument, NULL, 'p' }, { "saturation-offset", required_argument, NULL, 'o' }, { "deghosting", required_argument, NULL, 'd' }, { "noise-parameters", required_argument, NULL, 'n'}, { NULL, 0, NULL, 0 } }; int optionIndex = 0; while( ( c = getopt_long( argc, argv, "hvYc:g:r:f:s:m:b:p:xo:d:n:", cmdLineOptions, &optionIndex) ) != -1 ) { switch( c ) { /* help */ case 'h': printHelp(); throw QuietException(); /* verbose */ case 'v': verbose = true; break; /* gray scale */ case 'Y': opt_luminance = true; break; /* REMOVE: fix for Robertson -> remove in the future */ case 'F': opt_fillgaps = true; break; /* calibration method */ case 'c': if( strcmp( optarg, "mitsunaga" ) == 0 ) { opt_calibration = CALIBRATE; opt_method = MITSUNAGA_METHOD; } else if( strcmp( optarg, "robertson" ) == 0 ) { opt_calibration = CALIBRATE; opt_method = ROBERTSON_METHOD; } else if( strcmp( optarg, "none" ) == 0 ) { opt_calibration = NONE; opt_method = ROBERTSON_METHOD; } else throw pfs::Exception("unsupported automatic self-calibration method"); break; /* support of Gaussian weighting curve */ /* REMOVE: support 16 bit raw formats, in the long run switch to normalized 0-1 */ case 'g': opt_gauss = atof(optarg); if( opt_gauss <= 0.0f or opt_gauss > 1.0f) throw pfs::Exception("sigma value for Gaussian out of range. accepted range 0:1"); break; /* predefined response curve */ case 'r': if( strcmp( optarg, "linear" ) == 0 ) { opt_response = LINEAR; } else if( strcmp( optarg, "gamma" ) == 0 ) { opt_response = GAMMA; } else if( strcmp( optarg, "log" ) == 0 ) { opt_response = LOG10; } else { throw pfs::Exception("unknown standard response (check the manpage or use default)"); } opt_calibration = NONE; break; /* response curve from file */ case 'f': opt_response = FROM_FILE; opt_calibration = NONE; responseFile = fopen(optarg, "r"); if( !responseFile ) throw pfs::Exception("could not open file with response curve"); break; /* response curve to file */ case 's': responseSaveFile = fopen(optarg,"w"); if( !responseSaveFile ) throw pfs::Exception("could not open file to save response curve"); break; /* REMOVE: this is a hack option as well */ case 'm': input_multiplier = atof(optarg); if( input_multiplier<=0.0f ) throw pfs::Exception("input multiplier value out of range. accepted range >0"); break; /* bit depth of input files */ case 'b': opt_bpp = atoi(optarg); if( opt_bpp<8 || opt_bpp>32) throw pfs::Exception("bits per pixel value out of range. accepted range >=8"); break; /* internal parameters of Mitsunaga & Nayar's method */ case 'p': mitsunaga_sample_no = (unsigned long)atoll(optarg); if( mitsunaga_sample_no<10 || mitsunaga_sample_no >= (1 << 31)) throw pfs::Exception("too many samples"); break; case 'A': // max response opt_maxresponse = atoi(optarg); if( opt_maxresponse<=opt_minresponse ) throw pfs::Exception("max response should be higher than min response"); break; case 'S': // min response opt_minresponse = atoi(optarg); if( opt_minresponse<0 ) throw pfs::Exception("min response should be >0"); break; case 'o': opt_saturation_offset_perc = atof(optarg); if( opt_saturation_offset_perc < 0 || opt_saturation_offset_perc > 0.5 ) throw pfs::Exception("saturation offset should be between 0 and 0.5"); break; case 'd': if( !optarg ) opt_deghosting = 2; else opt_deghosting = atof(optarg); if( opt_deghosting<0 ) throw pfs::Exception("deghosting threshold should be >0"); break; case 'n': { int i = 0; if( isalpha(optarg[0]) ) { // Name of a camera, whose noise parameters are known, is provided for( int i = 0; i < sizeof(camera_presets)/sizeof(*camera_presets); i++ ) if( strcmp( optarg, camera_presets[i].camera_name ) == 0 ) camera = &camera_presets[i]; if( strcmp( optarg, "Empty" ) != 0 && strcmp( camera->camera_name, "Empty" ) == 0 ) throw pfs::Exception("specified camera noise parameters not found"); } else if( isdigit(optarg[0]) ) { // Custom noise parameters provided as a comma separated list stringstream sstream(optarg); string token; for( int cc = 0; cc < 3; cc++ ) { camera->camera_name = "Custom"; if( !getline(sstream, token, ',') ) throw pfs::Exception("invalid noise parameters"); camera->color_coefficients[cc] = atof(token.c_str()); } if( !getline(sstream, token, ',') ) throw pfs::Exception("invalid noise parameters"); camera->std_readout = atof(token.c_str()); if( !getline(sstream, token, ',') ) throw pfs::Exception("invalid noise parameters"); camera->std_adc = atof(token.c_str()); if( getline(sstream, token, ',') ) throw pfs::Exception("invalid noise parameters"); } else throw pfs::Exception("invalid noise parameters"); break; } case '?': throw QuietException(); case ':': throw QuietException(); } } /* FIX: this is most important, use fixed 0-1 range */ //!! FIX // in PFS streams, 8bit data are mapped to 0:1 range //Ivo: this seems to be the problem of >, code seems to assume 0:255 range of values // but 16bit values do not get multiplied, however, out of pfs they also come // in the range between 0:1, // proper solution: change code to work on the 0:1 range // if( opt_bpp == 8 ) // input_multiplier = 255; //Ivo end if( opt_method == ROBERTSON_METHOD ) { input_multiplier = float( 1 << opt_bpp ) - 1; opt_gauss *= input_multiplier; } if( opt_method == MITSUNAGA_METHOD ) input_multiplier = 1.0f; //--- verbose information and load initialization data VERBOSE_STR << "Assuming " << opt_bpp << " Bits per pixel in the LDR images (use --bpp to change this)" << endl; VERBOSE_STR << "calibrating channels: " << (opt_luminance ? "LUMINANCE" : "RGB") << endl; switch( opt_response ) { case FROM_FILE: VERBOSE_STR << "response curve from file" << endl; break; case LINEAR: VERBOSE_STR << "initial response: linear" << endl; break; case GAMMA: VERBOSE_STR << "initial response: gamma" << endl; break; case LOG10: VERBOSE_STR << "initial response: logarithmic" << endl; break; default: throw pfs::Exception("undefined standard response"); break; } if( opt_saturation_offset_perc==-1 ) { // By default, use 1% of the maximum value as the saturation offset (pixels above 99 of the max pixel value will be considered as saturated) opt_saturation_offset_perc = 0.01; } opt_saturation_offset = (int) (powf( 2.0f, opt_bpp )*opt_saturation_offset_perc + 0.5); // VERBOSE_STR << "interpolate missing parts of response: " // << (opt_fillgaps ? "yes" : "no") << endl; if( responseSaveFile != NULL ) VERBOSE_STR << "save response curve to a file" << endl; // number of input levels int M = (int) powf( 2.0f, opt_bpp ); VERBOSE_STR << "number of input levels: " << M << endl; VERBOSE_STR << "input multiplier: " << input_multiplier << endl; /* ------------------------------------------------------------------------------------------------ */ /* -------------------------------- initialization done ------------------------------------------- */ /* ------------------------------------------------------------------------------------------------ */ /* ------------------------------------------------------------------------------------------------ */ /* ---------------------------- preparation of images start --------------------------------------- */ /* ------------------------------------------------------------------------------------------------ */ //--- read frames from pfs stream int frame_no = 1; int width = 0; int height = 0; int size = 0; float minResponse = M; float maxResponse = 0.0f; // collected exposures ExposureList imgsY; ExposureList imgsR; ExposureList imgsG; ExposureList imgsB; while( true ) { pfs::Frame *frame = pfsio.readFrame( stdin ); if( frame == NULL ) break; // No more frames pfs::Channel *X = NULL; pfs::Channel *Y = NULL; pfs::Channel *Z = NULL; frame -> getXYZChannels( X, Y, Z ); if( X==NULL || Y==NULL || Z==NULL ) throw pfs::Exception( "missing XYZ channels in the PFS stream (try to preview your files using pfsview)" ); const char* exp_str = frame -> getTags() -> getString("BV"); if( exp_str == NULL ) throw pfs::Exception( "missing exposure information in the PFS stream (use pfsinhdrgen to input files)" ); // relate APEX brightness value only as a function of exposure time // that is assume aperture=1 and sensitivity=1 float exp_time = 1.0f / powf(2.0f,atof( exp_str )); float iso = 100.f; const char* iso_str = frame -> getTags() -> getString("ISO"); if( iso_str != NULL ) iso = atof( iso_str ); float aperture = 1.f; const char* aperture_str = frame -> getTags() -> getString("aperture"); if( aperture_str != NULL ) aperture = atof( aperture_str ); float raw_exposure_time = 1.f; const char* etime_str = frame -> getTags() -> getString("exposure_time"); if( etime_str != NULL ) raw_exposure_time = atof( etime_str ); // absolute calibration: this magic multiplier is a result of my // research in internet plus a bit of tweaking with a luminance // meter. tested with canon 350d, so might be a bit off for other // cameras. to control absolute calibration modify iso values in // hdrgen script or use pfsabsolute program. exp_time /= 1.0592f * 11.4f / 3.125f; VERBOSE_STR << "Exposure:" << exp_time << endl; VERBOSE_STR << "Aperture:" << aperture << endl; // frame size width = Y -> getCols(); height = Y -> getRows(); size = width * height; // new exposure image // in luminance only mode if ( opt_luminance ) { Exposure eY; eY.ti = exp_time; // The exposure times reported in the EXIF header, such as 1/40, are inaccurate and are // only meant to be instructive to photographers. More accurate exposure time is given when // the EXIF number is rounded to the nearest 1/3 in log-2 units (nearest 1/3 of f-stop). eY.exposure_time = powf( 2, roundf(log2f(raw_exposure_time)*3.f)/3.f); eY.iso = iso; eY.aperture = aperture; eY.yi = new pfs::Array2DImpl( width, height ); if( eY.yi == NULL ) throw pfs::Exception( "could not allocate memory for source exposure" ); for( int i=0 ; i < size ; i++ ) { (*eY.yi)( i ) = (*Y)( i ) * input_multiplier; float val = (*eY.yi)( i ); if( val>0.0f ) // discard zero values { maxResponse = (maxResponse > val) ? maxResponse : val; minResponse = (minResponse < val) ? minResponse : val; } } imgsY.push_back( eY ); } // new exposure image // collect RGB channels if not in luminance mode or we will apply // response to image (not in save response mode) if( !opt_luminance ) { /* PFS streams are in XYZ */ pfs::transformColorSpace( pfs::CS_XYZ, X, Y, Z, pfs::CS_RGB, X, Y, Z ); Exposure eR,eG,eB; eR.ti = eG.ti = eB.ti = exp_time; eR.iso = eG.iso = eB.iso = iso; eR.aperture = eG.aperture = eB.aperture = aperture; eR.exposure_time = eG.exposure_time = eB.exposure_time = raw_exposure_time; eR.yi = new pfs::Array2DImpl( width, height ); eG.yi = new pfs::Array2DImpl( width, height ); eB.yi = new pfs::Array2DImpl( width, height ); if( eR.yi==NULL || eG.yi==NULL || eB.yi==NULL ) throw pfs::Exception( "could not allocate memory for source exposure" ); for( int i = 0 ; i < size ; i++ ) { (*eR.yi)( i ) = (*X)( i ) * input_multiplier; (*eG.yi)( i ) = (*Y)( i ) * input_multiplier; (*eB.yi)( i ) = (*Z)( i ) * input_multiplier; float maxval = max3( (*eR.yi)( i ), (*eG.yi)( i ), (*eB.yi)( i ) ); float minval = min3( (*eR.yi)( i ), (*eG.yi)( i ), (*eB.yi)( i ) ); maxResponse = (maxResponse > maxval) ? maxResponse : maxval; minResponse = (minResponse < minval) ? minResponse : minval; } // add to exposures list imgsR.push_back( eR ); imgsG.push_back( eG ); imgsB.push_back( eB ); } VERBOSE_STR << "frame #" << frame_no << ", BV=" << atof(exp_str) << endl; frame_no++; pfsio.freeFrame( frame ); } if( frame_no < 2 ) throw pfs::Exception( "at least one image required for calibration (check paths in hdrgen script?)" ); // some more info on input frames VERBOSE_STR << "registered values: min=" << (int) minResponse << " max=" << (int) maxResponse << endl; if( opt_minresponse == -1 ) opt_minresponse = (int) minResponse; if( opt_maxresponse == -1 ) opt_maxresponse = (int) maxResponse; if( opt_response != FROM_FILE ) VERBOSE_STR << "camera response range: min=" << opt_minresponse << " max=" << opt_maxresponse << endl; if( maxResponse >= M ) throw pfs::Exception( "input value higher than defined number of input levels (adjust the number of bits per pixel)" ); /* ------------------------------------------------------------------------------------------------ */ /* ---------------------------- preparation of images done ---------------------------------------- */ /* ------------------------------------------------------------------------------------------------ */ /* ------------------------------------------------------------------------------------------------ */ /* ------------------- preparation of weighting and response functions ---------------------------- */ /* ------------------------------------------------------------------------------------------------ */ // weighting function representing confidence in accuracy of acquisition float* w = new float [ M ]; if( w == NULL ) throw pfs::Exception( "could not allocate memory for weighting function" ); /* 1.4: currently, weights are always Gaussian */ /* Composite function works better for extreme cases */ weightsComposite ( w, M, opt_minresponse, opt_maxresponse, opt_gauss ); // weightsGauss( w, M, opt_minresponse, opt_maxresponse, opt_gauss ); // camera response functions for each channel float* Iy = new float [ M ]; float* Ir = new float [ M ]; float* Ig = new float [ M ]; float* Ib = new float [ M ]; if( Iy == NULL || Ib == NULL || Ig == NULL || Ib == NULL ) throw pfs::Exception( "could not allocate memory for camera responses" ); // initial responses switch( opt_response ) { /* ------ Response function from file ------ */ case FROM_FILE: if( opt_luminance ) { bool loadY_ok = responseLoad ( responseFile, Iy, M ); bool loadW_ok = weightsLoad ( responseFile, w, M ); fclose( responseFile ); if( !loadY_ok || !loadW_ok ) throw pfs::Exception( "could not load response curve from file" ); } else { // read camera response from file (and also weights) bool loadR_ok = responseLoad ( responseFile, Ir, M ); bool loadG_ok = responseLoad ( responseFile, Ig, M ); bool loadB_ok = responseLoad ( responseFile, Ib, M ); bool loadW_ok = weightsLoad ( responseFile, w, M ); fclose( responseFile ); if( !loadR_ok || !loadG_ok || !loadB_ok || !loadW_ok ) throw pfs::Exception( "could not load response curve from file" ); } opt_calibration = NONE; break; /* ------ Response function linear ------ */ case LINEAR: responseLinear( Iy, M ); responseLinear( Ir, M ); responseLinear( Ig, M ); responseLinear( Ib, M ); break; /* ------ Response function gamma ------ */ case GAMMA: responseGamma( Iy, M ); responseGamma( Ir, M ); responseGamma( Ig, M ); responseGamma( Ib, M ); break; /* ------ Response function logarithmic ------ */ case LOG10: responseLog10( Iy, M ); responseLog10( Ir, M ); responseLog10( Ig, M ); responseLog10( Ib, M ); break; default: throw pfs::Exception( "camera response not initialized" ); break; } /* ------------------------------------------------------------------------------------------------ */ /* --------------- preparation of weighting and response functions done --------------------------- */ /* ------------------------------------------------------------------------------------------------ */ // create channels for output pfs::Frame *frame = pfsio.createFrame( width, height ); pfs::Channel *Xj = NULL; pfs::Channel *Yj = NULL; pfs::Channel *Zj = NULL; frame -> createXYZChannels( Xj, Yj, Zj ); /* REMOVE: -------------------------- */ // !!! this currently does more bad than good, relevant command line // option is disabled if( opt_fillgaps ) { if( opt_luminance ) { int num = responseFillGaps( Iy, w, M ); float perc = 100.0f * num / M; VERBOSE_STR << "interpolated " << perc << "% of the Y response curve..." << endl; } else { int numr = responseFillGaps ( Ir, w, M ); int numg = responseFillGaps ( Ig, w, M ); int numb = responseFillGaps ( Ib, w, M ); float perc = 100.0f * ( numr + numb + numg) / ( 3 * M ); VERBOSE_STR << "interpolated " << perc << "% of the RGB response curve..." << endl; } } /* REMOVE end: -------------------------- */ if( opt_calibration != NONE ) { } else VERBOSE_STR << "self-calibration disabled" << endl; /* ------------------------------------------------------------------------------------------------ */ /* ---------------------------------------- Calibration ------------------------------------------ */ /* ------------------------------------------------------------------------------------------------ */ /* counter for saturated pixels */ long sp = 0; if ( opt_calibration == CALIBRATE ) { switch ( opt_method ) { /* --------------------- Robertson Calibration --------------------- */ case ROBERTSON_METHOD: VERBOSE_STR << "automatic self-calibration method: robertson" << endl; if( opt_luminance ) { VERBOSE_STR << "recovering Y channel..." << endl; sp = robertson02_getResponse( Yj, imgsY, Iy, w, M); } else { VERBOSE_STR << "recovering R channel..." << endl; sp = robertson02_getResponse( Xj, imgsR, Ir, w, M); VERBOSE_STR << "recovering G channel..." << endl; sp += robertson02_getResponse( Yj, imgsG, Ig, w, M); VERBOSE_STR << "recovering B channel..." << endl; sp += robertson02_getResponse( Zj, imgsB, Ib, w, M); sp /= 3; } break; /* --------------------- Mitsunaga-Nayar Calibration ----------------- */ case MITSUNAGA_METHOD: VERBOSE_STR << "automatic self-calibration method: mitsunaga & nayar" << endl; if( opt_luminance ) throw pfs::Exception( "recovering Y channel not implemented, use robertson calibration." ); else { VERBOSE_STR << "Mitsunaga & Nayar ( " << mitsunaga_sample_no << " samples)" << endl; HDRCaptureMitsunaga* mits_calibration = new HDRCaptureMitsunaga(); mits_calibration -> capture ( imgsR, imgsG, imgsB, M, 3, Xj, Yj, Zj, mitsunaga_sample_no, Ir, Ig, Ib, w, 1 ); } break; default: throw pfs::Exception( "Calibration method not implemented" ); break; } } /* ------------------------------------------------------------------------------------------------ */ /* ---------------------------------------- Calibration done ------------------------------------- */ /* ------------------------------------------------------------------------------------------------ */ /* ------------------------------------------------------------------------------------------------ */ /* ---------------------------------------- HDR computation --------------------------------------- */ /* ------------------------------------------------------------------------------------------------ */ /* if( opt_calibration == NONE ) */ { switch ( opt_method ) { /* --------------------- Robertson Apply Curve --------------------- */ case ROBERTSON_METHOD: if( opt_luminance ) { VERBOSE_STR << "applying response to Y channel..." << endl; sp = robertson02_applyResponse( Yj, imgsY, Iy, w, M); } else { pfs::Array2D *RGB_out[3] = { Xj, Yj, Zj }; const ExposureList *exposures[] = { &imgsR, &imgsG, &imgsB }; const float *resp_curves[] = { Ir, Ig, Ib }; VERBOSE_STR << "applying response to RGB channels..." << endl; sp = robertson02_applyResponseRGB( RGB_out, exposures, resp_curves, w, M - opt_saturation_offset, camera, opt_deghosting); /* VERBOSE_STR << "applying response to R channel..." << endl; sp = robertson02_applyResponse( Xj, imgsR, Ir, w, M); VERBOSE_STR << "applying response to G channel..." << endl; sp += robertson02_applyResponse( Yj, imgsG, Ig, w, M); VERBOSE_STR << "applying response to B channel..." << endl; sp += robertson02_applyResponse( Zj, imgsB, Ib, w, M); sp /= 3;*/ if( sp > 0 ) { float perc = ceilf( 100.0f * sp / size ); VERBOSE_STR << "saturated pixels found in " << perc << "% of the image!" << endl; } } break; /* --------------------- Mitsunaga-Nayar Apply Curve --------------------- */ case MITSUNAGA_METHOD: if( opt_luminance ) throw pfs::Exception( "recovering Y channel not implemented, use robertson calibration." ); else { HDRCaptureMitsunaga* mits_calibration = new HDRCaptureMitsunaga(); mits_calibration -> capture ( imgsR, imgsG, imgsB, M, 3, Xj, Yj, Zj, mitsunaga_sample_no, Ir, Ig, Ib, w, 0 ); // normalization VERBOSE_STR << "Normalization" << endl; float mmax = -1e30; for( int j = 0; j < size; j++ ) { float maxval = max3( (*Xj)( j ),(*Yj)( j ),(*Zj)( j ) ); mmax = ( mmax > maxval ) ? mmax : maxval; } for( int j = 0; j < size; j++ ) { (*Xj)( j ) /= mmax; (*Yj)( j ) /= mmax; (*Zj)( j ) /= mmax; } } break; } } /* ------------------------------------------------------------------------------------------------ */ /* ---------------------------------------- HDR computation done----------------------------------- */ /* ------------------------------------------------------------------------------------------------ */ /* ------------------------------------------------------------------------------------------------ */ /* --------------------------------------------- Save Output -------------------------------------- */ /* ------------------------------------------------------------------------------------------------ */ // save response curve to a given file if( responseSaveFile != NULL ) { if( opt_luminance ) { responseSave( responseSaveFile, Iy, M, "IY" ); weightsSave ( responseSaveFile, w, M, "W" ); fclose( responseSaveFile ); } else { responseSave( responseSaveFile, Ir, M, "IR" ); responseSave( responseSaveFile, Ig, M, "IG" ); responseSave( responseSaveFile, Ib, M, "IB" ); weightsSave ( responseSaveFile, w, M, "W"); fclose( responseSaveFile ); } } // output PFS stream with calibrated response in any case if( opt_luminance ) for( int i=0 ; i #include _BEGIN_EXTERN_C extern _GETOPT_API int optind; extern _GETOPT_API int opterr; extern _GETOPT_API int optopt; // Ansi struct option_a { const char* name; int has_arg; int *flag; char val; }; extern _GETOPT_API char *optarg_a; extern _GETOPT_API int getopt_a(int argc, char *const *argv, const char *optstring) _GETOPT_THROW; extern _GETOPT_API int getopt_long_a(int argc, char *const *argv, const char *options, const struct option_a *long_options, int *opt_index) _GETOPT_THROW; extern _GETOPT_API int getopt_long_only_a(int argc, char *const *argv, const char *options, const struct option_a *long_options, int *opt_index) _GETOPT_THROW; // Unicode struct option_w { const wchar_t* name; int has_arg; int *flag; wchar_t val; }; extern _GETOPT_API wchar_t *optarg_w; extern _GETOPT_API int getopt_w(int argc, wchar_t *const *argv, const wchar_t *optstring) _GETOPT_THROW; extern _GETOPT_API int getopt_long_w(int argc, wchar_t *const *argv, const wchar_t *options, const struct option_w *long_options, int *opt_index) _GETOPT_THROW; extern _GETOPT_API int getopt_long_only_w(int argc, wchar_t *const *argv, const wchar_t *options, const struct option_w *long_options, int *opt_index) _GETOPT_THROW; _END_EXTERN_C #undef _BEGIN_EXTERN_C #undef _END_EXTERN_C #undef _GETOPT_THROW #undef _GETOPT_API #ifdef _UNICODE #define getopt getopt_w #define getopt_long getopt_long_w #define getopt_long_only getopt_long_only_w #define option option_w #define optarg optarg_w #else #define getopt getopt_a #define getopt_long getopt_long_a #define getopt_long_only getopt_long_only_a #define option option_a #define optarg optarg_a #endif #endif // __GETOPT_H_ pfstools-2.2.0/src/getopt/getopt.c0000664000701400070140000006536614105165614015640 0ustar rkm38rkm38/* Getopt for Microsoft C This code is a modification of the Free Software Foundation, Inc. Getopt library for parsing command line argument the purpose was to provide a Microsoft Visual C friendly derivative. This code provides functionality for both Unicode and Multibyte builds. Date: 02/03/2011 - Ludvik Jerabek - Initial Release Version: 1.0 Comment: Supports getopt, getopt_long, and getopt_long_only and POSIXLY_CORRECT environment flag License: LGPL Revisions: 02/03/2011 - Ludvik Jerabek - Initial Release 02/20/2011 - Ludvik Jerabek - Fixed compiler warnings at Level 4 07/05/2011 - Ludvik Jerabek - Added no_argument, required_argument, optional_argument defs 08/03/2011 - Ludvik Jerabek - Fixed non-argument runtime bug which caused runtime exception 08/09/2011 - Ludvik Jerabek - Added code to export functions for DLL and LIB 02/15/2012 - Ludvik Jerabek - Fixed _GETOPT_THROW definition missing in implementation file 08/01/2012 - Ludvik Jerabek - Created separate functions for char and wchar_t characters so single dll can do both unicode and ansi 10/15/2012 - Ludvik Jerabek - Modified to match latest GNU features **DISCLAIMER** THIS MATERIAL IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING, BUT Not LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED WARRANTIES, SO THE ABOVE EXCLUSION MAY NOT APPLY TO YOU. IN NO EVENT WILL I BE LIABLE TO ANY PARTY FOR ANY DIRECT, INDIRECT, SPECIAL OR OTHER CONSEQUENTIAL DAMAGES FOR ANY USE OF THIS MATERIAL INCLUDING, WITHOUT LIMITATION, ANY LOST PROFITS, BUSINESS INTERRUPTION, LOSS OF PROGRAMS OR OTHER DATA ON YOUR INFORMATION HANDLING SYSTEM OR OTHERWISE, EVEN If WE ARE EXPRESSLY ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. */ #define _CRT_SECURE_NO_WARNINGS #include #include #include #include "getopt.h" #ifdef __cplusplus #define _GETOPT_THROW throw() #else #define _GETOPT_THROW #endif int optind = 1; int opterr = 1; int optopt = '?'; enum ENUM_ORDERING { REQUIRE_ORDER, PERMUTE, RETURN_IN_ORDER }; // // // Ansi structures and functions follow // // static struct _getopt_data_a { int optind; int opterr; int optopt; char *optarg; int __initialized; char *__nextchar; enum ENUM_ORDERING __ordering; int __posixly_correct; int __first_nonopt; int __last_nonopt; } getopt_data_a; char *optarg_a; static void exchange_a(char **argv, struct _getopt_data_a *d) { int bottom = d->__first_nonopt; int middle = d->__last_nonopt; int top = d->optind; char *tem; while (top > middle && middle > bottom) { if (top - middle > middle - bottom) { int len = middle - bottom; register int i; for (i = 0; i < len; i++) { tem = argv[bottom + i]; argv[bottom + i] = argv[top - (middle - bottom) + i]; argv[top - (middle - bottom) + i] = tem; } top -= len; } else { int len = top - middle; register int i; for (i = 0; i < len; i++) { tem = argv[bottom + i]; argv[bottom + i] = argv[middle + i]; argv[middle + i] = tem; } bottom += len; } } d->__first_nonopt += (d->optind - d->__last_nonopt); d->__last_nonopt = d->optind; } static const char *_getopt_initialize_a (const char *optstring, struct _getopt_data_a *d, int posixly_correct) { d->__first_nonopt = d->__last_nonopt = d->optind; d->__nextchar = NULL; d->__posixly_correct = posixly_correct | !!getenv("POSIXLY_CORRECT"); if (optstring[0] == '-') { d->__ordering = RETURN_IN_ORDER; ++optstring; } else if (optstring[0] == '+') { d->__ordering = REQUIRE_ORDER; ++optstring; } else if (d->__posixly_correct) d->__ordering = REQUIRE_ORDER; else d->__ordering = PERMUTE; return optstring; } int _getopt_internal_r_a (int argc, char *const *argv, const char *optstring, const struct option_a *longopts, int *longind, int long_only, struct _getopt_data_a *d, int posixly_correct) { int print_errors = d->opterr; if (argc < 1) return -1; d->optarg = NULL; if (d->optind == 0 || !d->__initialized) { if (d->optind == 0) d->optind = 1; optstring = _getopt_initialize_a (optstring, d, posixly_correct); d->__initialized = 1; } else if (optstring[0] == '-' || optstring[0] == '+') optstring++; if (optstring[0] == ':') print_errors = 0; if (d->__nextchar == NULL || *d->__nextchar == '\0') { if (d->__last_nonopt > d->optind) d->__last_nonopt = d->optind; if (d->__first_nonopt > d->optind) d->__first_nonopt = d->optind; if (d->__ordering == PERMUTE) { if (d->__first_nonopt != d->__last_nonopt && d->__last_nonopt != d->optind) exchange_a ((char **) argv, d); else if (d->__last_nonopt != d->optind) d->__first_nonopt = d->optind; while (d->optind < argc && (argv[d->optind][0] != '-' || argv[d->optind][1] == '\0')) d->optind++; d->__last_nonopt = d->optind; } if (d->optind != argc && !strcmp(argv[d->optind], "--")) { d->optind++; if (d->__first_nonopt != d->__last_nonopt && d->__last_nonopt != d->optind) exchange_a((char **) argv, d); else if (d->__first_nonopt == d->__last_nonopt) d->__first_nonopt = d->optind; d->__last_nonopt = argc; d->optind = argc; } if (d->optind == argc) { if (d->__first_nonopt != d->__last_nonopt) d->optind = d->__first_nonopt; return -1; } if ((argv[d->optind][0] != '-' || argv[d->optind][1] == '\0')) { if (d->__ordering == REQUIRE_ORDER) return -1; d->optarg = argv[d->optind++]; return 1; } d->__nextchar = (argv[d->optind] + 1 + (longopts != NULL && argv[d->optind][1] == '-')); } if (longopts != NULL && (argv[d->optind][1] == '-' || (long_only && (argv[d->optind][2] || !strchr(optstring, argv[d->optind][1]))))) { char *nameend; unsigned int namelen; const struct option_a *p; const struct option_a *pfound = NULL; struct option_list { const struct option_a *p; struct option_list *next; } *ambig_list = NULL; int exact = 0; int indfound = -1; int option_index; for (nameend = d->__nextchar; *nameend && *nameend != '='; nameend++); namelen = (unsigned int)(nameend - d->__nextchar); for (p = longopts, option_index = 0; p->name; p++, option_index++) if (!strncmp(p->name, d->__nextchar, namelen)) { if (namelen == (unsigned int)strlen(p->name)) { pfound = p; indfound = option_index; exact = 1; break; } else if (pfound == NULL) { pfound = p; indfound = option_index; } else if (long_only || pfound->has_arg != p->has_arg || pfound->flag != p->flag || pfound->val != p->val) { struct option_list *newp = (struct option_list*)alloca(sizeof(*newp)); newp->p = p; newp->next = ambig_list; ambig_list = newp; } } if (ambig_list != NULL && !exact) { if (print_errors) { struct option_list first; first.p = pfound; first.next = ambig_list; ambig_list = &first; fprintf (stderr, "%s: option '%s' is ambiguous; possibilities:", argv[0], argv[d->optind]); do { fprintf (stderr, " '--%s'", ambig_list->p->name); ambig_list = ambig_list->next; } while (ambig_list != NULL); fputc ('\n', stderr); } d->__nextchar += strlen(d->__nextchar); d->optind++; d->optopt = 0; return '?'; } if (pfound != NULL) { option_index = indfound; d->optind++; if (*nameend) { if (pfound->has_arg) d->optarg = nameend + 1; else { if (print_errors) { if (argv[d->optind - 1][1] == '-') { fprintf(stderr, "%s: option '--%s' doesn't allow an argument\n",argv[0], pfound->name); } else { fprintf(stderr, "%s: option '%c%s' doesn't allow an argument\n",argv[0], argv[d->optind - 1][0],pfound->name); } } d->__nextchar += strlen(d->__nextchar); d->optopt = pfound->val; return '?'; } } else if (pfound->has_arg == 1) { if (d->optind < argc) d->optarg = argv[d->optind++]; else { if (print_errors) { fprintf(stderr,"%s: option '--%s' requires an argument\n",argv[0], pfound->name); } d->__nextchar += strlen(d->__nextchar); d->optopt = pfound->val; return optstring[0] == ':' ? ':' : '?'; } } d->__nextchar += strlen(d->__nextchar); if (longind != NULL) *longind = option_index; if (pfound->flag) { *(pfound->flag) = pfound->val; return 0; } return pfound->val; } if (!long_only || argv[d->optind][1] == '-' || strchr(optstring, *d->__nextchar) == NULL) { if (print_errors) { if (argv[d->optind][1] == '-') { fprintf(stderr, "%s: unrecognized option '--%s'\n",argv[0], d->__nextchar); } else { fprintf(stderr, "%s: unrecognized option '%c%s'\n",argv[0], argv[d->optind][0], d->__nextchar); } } d->__nextchar = (char *)""; d->optind++; d->optopt = 0; return '?'; } } { char c = *d->__nextchar++; char *temp = (char*)strchr(optstring, c); if (*d->__nextchar == '\0') ++d->optind; if (temp == NULL || c == ':' || c == ';') { if (print_errors) { fprintf(stderr, "%s: invalid option -- '%c'\n", argv[0], c); } d->optopt = c; return '?'; } if (temp[0] == 'W' && temp[1] == ';') { char *nameend; const struct option_a *p; const struct option_a *pfound = NULL; int exact = 0; int ambig = 0; int indfound = 0; int option_index; if (longopts == NULL) goto no_longs; if (*d->__nextchar != '\0') { d->optarg = d->__nextchar; d->optind++; } else if (d->optind == argc) { if (print_errors) { fprintf(stderr,"%s: option requires an argument -- '%c'\n",argv[0], c); } d->optopt = c; if (optstring[0] == ':') c = ':'; else c = '?'; return c; } else d->optarg = argv[d->optind++]; for (d->__nextchar = nameend = d->optarg; *nameend && *nameend != '='; nameend++); for (p = longopts, option_index = 0; p->name; p++, option_index++) if (!strncmp(p->name, d->__nextchar, nameend - d->__nextchar)) { if ((unsigned int) (nameend - d->__nextchar) == strlen(p->name)) { pfound = p; indfound = option_index; exact = 1; break; } else if (pfound == NULL) { pfound = p; indfound = option_index; } else if (long_only || pfound->has_arg != p->has_arg || pfound->flag != p->flag || pfound->val != p->val) ambig = 1; } if (ambig && !exact) { if (print_errors) { fprintf(stderr, "%s: option '-W %s' is ambiguous\n",argv[0], d->optarg); } d->__nextchar += strlen(d->__nextchar); d->optind++; return '?'; } if (pfound != NULL) { option_index = indfound; if (*nameend) { if (pfound->has_arg) d->optarg = nameend + 1; else { if (print_errors) { fprintf(stderr, "%s: option '-W %s' doesn't allow an argument\n",argv[0], pfound->name); } d->__nextchar += strlen(d->__nextchar); return '?'; } } else if (pfound->has_arg == 1) { if (d->optind < argc) d->optarg = argv[d->optind++]; else { if (print_errors) { fprintf(stderr, "%s: option '-W %s' requires an argument\n",argv[0], pfound->name); } d->__nextchar += strlen(d->__nextchar); return optstring[0] == ':' ? ':' : '?'; } } else d->optarg = NULL; d->__nextchar += strlen(d->__nextchar); if (longind != NULL) *longind = option_index; if (pfound->flag) { *(pfound->flag) = pfound->val; return 0; } return pfound->val; } no_longs: d->__nextchar = NULL; return 'W'; } if (temp[1] == ':') { if (temp[2] == ':') { if (*d->__nextchar != '\0') { d->optarg = d->__nextchar; d->optind++; } else d->optarg = NULL; d->__nextchar = NULL; } else { if (*d->__nextchar != '\0') { d->optarg = d->__nextchar; d->optind++; } else if (d->optind == argc) { if (print_errors) { fprintf(stderr,"%s: option requires an argument -- '%c'\n",argv[0], c); } d->optopt = c; if (optstring[0] == ':') c = ':'; else c = '?'; } else d->optarg = argv[d->optind++]; d->__nextchar = NULL; } } return c; } } int _getopt_internal_a (int argc, char *const *argv, const char *optstring, const struct option_a *longopts, int *longind, int long_only, int posixly_correct) { int result; getopt_data_a.optind = optind; getopt_data_a.opterr = opterr; result = _getopt_internal_r_a (argc, argv, optstring, longopts,longind, long_only, &getopt_data_a,posixly_correct); optind = getopt_data_a.optind; optarg_a = getopt_data_a.optarg; optopt = getopt_data_a.optopt; return result; } int getopt_a (int argc, char *const *argv, const char *optstring) _GETOPT_THROW { return _getopt_internal_a (argc, argv, optstring, (const struct option_a *) 0, (int *) 0, 0, 0); } int getopt_long_a (int argc, char *const *argv, const char *options, const struct option_a *long_options, int *opt_index) _GETOPT_THROW { return _getopt_internal_a (argc, argv, options, long_options, opt_index, 0, 0); } int getopt_long_only_a (int argc, char *const *argv, const char *options, const struct option_a *long_options, int *opt_index) _GETOPT_THROW { return _getopt_internal_a (argc, argv, options, long_options, opt_index, 1, 0); } int _getopt_long_r_a (int argc, char *const *argv, const char *options, const struct option_a *long_options, int *opt_index, struct _getopt_data_a *d) { return _getopt_internal_r_a (argc, argv, options, long_options, opt_index,0, d, 0); } int _getopt_long_only_r_a (int argc, char *const *argv, const char *options, const struct option_a *long_options, int *opt_index, struct _getopt_data_a *d) { return _getopt_internal_r_a (argc, argv, options, long_options, opt_index, 1, d, 0); } // // // Unicode Structures and Functions // // static struct _getopt_data_w { int optind; int opterr; int optopt; wchar_t *optarg; int __initialized; wchar_t *__nextchar; enum ENUM_ORDERING __ordering; int __posixly_correct; int __first_nonopt; int __last_nonopt; } getopt_data_w; wchar_t *optarg_w; static void exchange_w(wchar_t **argv, struct _getopt_data_w *d) { int bottom = d->__first_nonopt; int middle = d->__last_nonopt; int top = d->optind; wchar_t *tem; while (top > middle && middle > bottom) { if (top - middle > middle - bottom) { int len = middle - bottom; register int i; for (i = 0; i < len; i++) { tem = argv[bottom + i]; argv[bottom + i] = argv[top - (middle - bottom) + i]; argv[top - (middle - bottom) + i] = tem; } top -= len; } else { int len = top - middle; register int i; for (i = 0; i < len; i++) { tem = argv[bottom + i]; argv[bottom + i] = argv[middle + i]; argv[middle + i] = tem; } bottom += len; } } d->__first_nonopt += (d->optind - d->__last_nonopt); d->__last_nonopt = d->optind; } static const wchar_t *_getopt_initialize_w (const wchar_t *optstring, struct _getopt_data_w *d, int posixly_correct) { d->__first_nonopt = d->__last_nonopt = d->optind; d->__nextchar = NULL; d->__posixly_correct = posixly_correct | !!_wgetenv(L"POSIXLY_CORRECT"); if (optstring[0] == L'-') { d->__ordering = RETURN_IN_ORDER; ++optstring; } else if (optstring[0] == L'+') { d->__ordering = REQUIRE_ORDER; ++optstring; } else if (d->__posixly_correct) d->__ordering = REQUIRE_ORDER; else d->__ordering = PERMUTE; return optstring; } int _getopt_internal_r_w (int argc, wchar_t *const *argv, const wchar_t *optstring, const struct option_w *longopts, int *longind, int long_only, struct _getopt_data_w *d, int posixly_correct) { int print_errors = d->opterr; if (argc < 1) return -1; d->optarg = NULL; if (d->optind == 0 || !d->__initialized) { if (d->optind == 0) d->optind = 1; optstring = _getopt_initialize_w (optstring, d, posixly_correct); d->__initialized = 1; } else if (optstring[0] == L'-' || optstring[0] == L'+') optstring++; if (optstring[0] == L':') print_errors = 0; if (d->__nextchar == NULL || *d->__nextchar == L'\0') { if (d->__last_nonopt > d->optind) d->__last_nonopt = d->optind; if (d->__first_nonopt > d->optind) d->__first_nonopt = d->optind; if (d->__ordering == PERMUTE) { if (d->__first_nonopt != d->__last_nonopt && d->__last_nonopt != d->optind) exchange_w((wchar_t **) argv, d); else if (d->__last_nonopt != d->optind) d->__first_nonopt = d->optind; while (d->optind < argc && (argv[d->optind][0] != L'-' || argv[d->optind][1] == L'\0')) d->optind++; d->__last_nonopt = d->optind; } if (d->optind != argc && !wcscmp(argv[d->optind], L"--")) { d->optind++; if (d->__first_nonopt != d->__last_nonopt && d->__last_nonopt != d->optind) exchange_w((wchar_t **) argv, d); else if (d->__first_nonopt == d->__last_nonopt) d->__first_nonopt = d->optind; d->__last_nonopt = argc; d->optind = argc; } if (d->optind == argc) { if (d->__first_nonopt != d->__last_nonopt) d->optind = d->__first_nonopt; return -1; } if ((argv[d->optind][0] != L'-' || argv[d->optind][1] == L'\0')) { if (d->__ordering == REQUIRE_ORDER) return -1; d->optarg = argv[d->optind++]; return 1; } d->__nextchar = (argv[d->optind] + 1 + (longopts != NULL && argv[d->optind][1] == L'-')); } if (longopts != NULL && (argv[d->optind][1] == L'-' || (long_only && (argv[d->optind][2] || !wcschr(optstring, argv[d->optind][1]))))) { wchar_t *nameend; unsigned int namelen; const struct option_w *p; const struct option_w *pfound = NULL; struct option_list { const struct option_w *p; struct option_list *next; } *ambig_list = NULL; int exact = 0; int indfound = -1; int option_index; for (nameend = d->__nextchar; *nameend && *nameend != L'='; nameend++); namelen = (unsigned int)(nameend - d->__nextchar); for (p = longopts, option_index = 0; p->name; p++, option_index++) if (!wcsncmp(p->name, d->__nextchar, namelen)) { if (namelen == (unsigned int)wcslen(p->name)) { pfound = p; indfound = option_index; exact = 1; break; } else if (pfound == NULL) { pfound = p; indfound = option_index; } else if (long_only || pfound->has_arg != p->has_arg || pfound->flag != p->flag || pfound->val != p->val) { struct option_list *newp = (struct option_list*)alloca(sizeof(*newp)); newp->p = p; newp->next = ambig_list; ambig_list = newp; } } if (ambig_list != NULL && !exact) { if (print_errors) { struct option_list first; first.p = pfound; first.next = ambig_list; ambig_list = &first; fwprintf(stderr, L"%s: option '%s' is ambiguous; possibilities:", argv[0], argv[d->optind]); do { fwprintf (stderr, L" '--%s'", ambig_list->p->name); ambig_list = ambig_list->next; } while (ambig_list != NULL); fputwc (L'\n', stderr); } d->__nextchar += wcslen(d->__nextchar); d->optind++; d->optopt = 0; return L'?'; } if (pfound != NULL) { option_index = indfound; d->optind++; if (*nameend) { if (pfound->has_arg) d->optarg = nameend + 1; else { if (print_errors) { if (argv[d->optind - 1][1] == L'-') { fwprintf(stderr, L"%s: option '--%s' doesn't allow an argument\n",argv[0], pfound->name); } else { fwprintf(stderr, L"%s: option '%c%s' doesn't allow an argument\n",argv[0], argv[d->optind - 1][0],pfound->name); } } d->__nextchar += wcslen(d->__nextchar); d->optopt = pfound->val; return L'?'; } } else if (pfound->has_arg == 1) { if (d->optind < argc) d->optarg = argv[d->optind++]; else { if (print_errors) { fwprintf(stderr,L"%s: option '--%s' requires an argument\n",argv[0], pfound->name); } d->__nextchar += wcslen(d->__nextchar); d->optopt = pfound->val; return optstring[0] == L':' ? L':' : L'?'; } } d->__nextchar += wcslen(d->__nextchar); if (longind != NULL) *longind = option_index; if (pfound->flag) { *(pfound->flag) = pfound->val; return 0; } return pfound->val; } if (!long_only || argv[d->optind][1] == L'-' || wcschr(optstring, *d->__nextchar) == NULL) { if (print_errors) { if (argv[d->optind][1] == L'-') { fwprintf(stderr, L"%s: unrecognized option '--%s'\n",argv[0], d->__nextchar); } else { fwprintf(stderr, L"%s: unrecognized option '%c%s'\n",argv[0], argv[d->optind][0], d->__nextchar); } } d->__nextchar = (wchar_t *)L""; d->optind++; d->optopt = 0; return L'?'; } } { wchar_t c = *d->__nextchar++; wchar_t *temp = (wchar_t*)wcschr(optstring, c); if (*d->__nextchar == L'\0') ++d->optind; if (temp == NULL || c == L':' || c == L';') { if (print_errors) { fwprintf(stderr, L"%s: invalid option -- '%c'\n", argv[0], c); } d->optopt = c; return L'?'; } if (temp[0] == L'W' && temp[1] == L';') { wchar_t *nameend; const struct option_w *p; const struct option_w *pfound = NULL; int exact = 0; int ambig = 0; int indfound = 0; int option_index; if (longopts == NULL) goto no_longs; if (*d->__nextchar != L'\0') { d->optarg = d->__nextchar; d->optind++; } else if (d->optind == argc) { if (print_errors) { fwprintf(stderr,L"%s: option requires an argument -- '%c'\n",argv[0], c); } d->optopt = c; if (optstring[0] == L':') c = L':'; else c = L'?'; return c; } else d->optarg = argv[d->optind++]; for (d->__nextchar = nameend = d->optarg; *nameend && *nameend != L'='; nameend++); for (p = longopts, option_index = 0; p->name; p++, option_index++) if (!wcsncmp(p->name, d->__nextchar, nameend - d->__nextchar)) { if ((unsigned int) (nameend - d->__nextchar) == wcslen(p->name)) { pfound = p; indfound = option_index; exact = 1; break; } else if (pfound == NULL) { pfound = p; indfound = option_index; } else if (long_only || pfound->has_arg != p->has_arg || pfound->flag != p->flag || pfound->val != p->val) ambig = 1; } if (ambig && !exact) { if (print_errors) { fwprintf(stderr, L"%s: option '-W %s' is ambiguous\n",argv[0], d->optarg); } d->__nextchar += wcslen(d->__nextchar); d->optind++; return L'?'; } if (pfound != NULL) { option_index = indfound; if (*nameend) { if (pfound->has_arg) d->optarg = nameend + 1; else { if (print_errors) { fwprintf(stderr, L"%s: option '-W %s' doesn't allow an argument\n",argv[0], pfound->name); } d->__nextchar += wcslen(d->__nextchar); return L'?'; } } else if (pfound->has_arg == 1) { if (d->optind < argc) d->optarg = argv[d->optind++]; else { if (print_errors) { fwprintf(stderr, L"%s: option '-W %s' requires an argument\n",argv[0], pfound->name); } d->__nextchar += wcslen(d->__nextchar); return optstring[0] == L':' ? L':' : L'?'; } } else d->optarg = NULL; d->__nextchar += wcslen(d->__nextchar); if (longind != NULL) *longind = option_index; if (pfound->flag) { *(pfound->flag) = pfound->val; return 0; } return pfound->val; } no_longs: d->__nextchar = NULL; return L'W'; } if (temp[1] == L':') { if (temp[2] == L':') { if (*d->__nextchar != L'\0') { d->optarg = d->__nextchar; d->optind++; } else d->optarg = NULL; d->__nextchar = NULL; } else { if (*d->__nextchar != L'\0') { d->optarg = d->__nextchar; d->optind++; } else if (d->optind == argc) { if (print_errors) { fwprintf(stderr,L"%s: option requires an argument -- '%c'\n",argv[0], c); } d->optopt = c; if (optstring[0] == L':') c = L':'; else c = L'?'; } else d->optarg = argv[d->optind++]; d->__nextchar = NULL; } } return c; } } int _getopt_internal_w (int argc, wchar_t *const *argv, const wchar_t *optstring, const struct option_w *longopts, int *longind, int long_only, int posixly_correct) { int result; getopt_data_w.optind = optind; getopt_data_w.opterr = opterr; result = _getopt_internal_r_w (argc, argv, optstring, longopts,longind, long_only, &getopt_data_w,posixly_correct); optind = getopt_data_w.optind; optarg_w = getopt_data_w.optarg; optopt = getopt_data_w.optopt; return result; } int getopt_w (int argc, wchar_t *const *argv, const wchar_t *optstring) _GETOPT_THROW { return _getopt_internal_w (argc, argv, optstring, (const struct option_w *) 0, (int *) 0, 0, 0); } int getopt_long_w (int argc, wchar_t *const *argv, const wchar_t *options, const struct option_w *long_options, int *opt_index) _GETOPT_THROW { return _getopt_internal_w (argc, argv, options, long_options, opt_index, 0, 0); } int getopt_long_only_w (int argc, wchar_t *const *argv, const wchar_t *options, const struct option_w *long_options, int *opt_index) _GETOPT_THROW { return _getopt_internal_w (argc, argv, options, long_options, opt_index, 1, 0); } int _getopt_long_r_w (int argc, wchar_t *const *argv, const wchar_t *options, const struct option_w *long_options, int *opt_index, struct _getopt_data_w *d) { return _getopt_internal_r_w (argc, argv, options, long_options, opt_index,0, d, 0); } int _getopt_long_only_r_w (int argc, wchar_t *const *argv, const wchar_t *options, const struct option_w *long_options, int *opt_index, struct _getopt_data_w *d) { return _getopt_internal_r_w (argc, argv, options, long_options, opt_index, 1, d, 0); }pfstools-2.2.0/src/CMakeLists.txt0000664000701400070140000000260614105165614015414 0ustar rkm38rkm38if( NOT HAS_GETOPT ) add_subdirectory (getopt) endif( NOT HAS_GETOPT ) add_subdirectory (pfs) add_subdirectory (fileformat) add_subdirectory (filter) if( ImageMagick_FOUND ) add_subdirectory (hdrhtml) endif( ImageMagick_FOUND ) if( Qt5Widgets_FOUND ) add_subdirectory (pfsview) else( Qt5Widgets_FOUND ) # When pre-compiled pfsview is installed on Windows find_program( PFSVIEW pfsview ) if( PFSVIEW ) message( "Found (precompiled) pfsview. pfsv script will be installed." ) # Replace the tag with the path to bash file(READ ${CMAKE_CURRENT_SOURCE_DIR}/pfsview/pfsv.in file_content) string(REGEX REPLACE "@BASH_PATH@" "${BASH_EXECUTABLE}" file_content "${file_content}") file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/pfsview/pfsv" "${file_content}") install (FILES "${CMAKE_CURRENT_BINARY_DIR}/pfsview/pfsv" PERMISSIONS OWNER_EXECUTE GROUP_EXECUTE WORLD_EXECUTE OWNER_WRITE WORLD_READ GROUP_READ OWNER_READ DESTINATION bin) endif( PFSVIEW ) endif( Qt5Widgets_FOUND ) if( MATLAB_FOUND ) add_subdirectory (matlab) endif( MATLAB_FOUND ) if( OPENGL_FOUND AND GLUT_FOUND ) add_subdirectory (pfsglview) endif( OPENGL_FOUND AND GLUT_FOUND ) if( WITH_HDRVC ) add_subdirectory (hdrvc) endif( WITH_HDRVC ) if( MKOCTFILE ) add_subdirectory (octave) endif( MKOCTFILE ) add_subdirectory (tmo) add_subdirectory (camera) pfstools-2.2.0/src/pfsview/0002775000701400070140000000000014105165615014336 5ustar rkm38rkm38pfstools-2.2.0/src/pfsview/pfsview_widget.h0000664000701400070140000000770314105165614017541 0ustar rkm38rkm38#ifndef PFSVIEW_WIDGET_H #define PFSVIEW_WIDGET_H /** * @brief * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Rafal Mantiuk and Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Rafal Mantiuk, * * $Id: pfsview_widget.h,v 1.10 2011/04/30 14:01:17 rafm Exp $ */ #include #include #include #include #include #include #include namespace pfs { class DOMIO; class Frame; } enum RGBClippingMethod { CLIP_SIMPLE = 0, CLIP_COLORCODED, CLIP_KEEP_BRI_HUE, CLIP_COUNT }; enum LumMappingMethod { MAP_LINEAR, MAP_GAMMA1_4, MAP_GAMMA1_8, MAP_GAMMA2_2, MAP_GAMMA2_6, MAP_LOGARITHMIC }; enum InfNaNTreatment { INFNAN_HIDE=0, INFNAN_MARK_AS_RED }; enum NegativeTreatment { NEGATIVE_BLACK=0, NEGATIVE_MARK_AS_RED, NEGATIVE_GREEN_SCALE, NEGATIVE_ABSOLUTE, NEGATIVE_COUNT }; struct PointerValue { int x, y; float value[3]; int valuesUsed; bool valid; }; class PFSViewWidgetArea : public QScrollArea { Q_OBJECT public: PFSViewWidgetArea( QWidget *parent=0 ); QSize sizeHint() const; }; class PFSViewWidget : public QWidget { Q_OBJECT public: PFSViewWidget( QWidget *parent=0 ); ~PFSViewWidget(); public slots: void zoomIn(); void zoomOut(); void zoomOriginal(); void setRGBClippingMethod( QAction *action ); void setInfNaNTreatment( QAction *action ); void setNegativeTreatment( QAction *action ); void setLumMappingMethod( int method ); signals: void updatePointerValue(); protected: // void paintEvent( QPaintEvent * ); void updateZoom(); void updateMapping(); void postUpdateMapping(); void mouseMoveEvent( QMouseEvent *mouseEvent ); void setPointer( int x = -1, int y = -1 ); void leaveEvent( QEvent * ); void paintEvent( QPaintEvent *event ); private: pfs::Frame *pfsFrame; const char *visibleChannel; QByteArray visibleChannelName; bool colorChannelsPresent; bool updateMappingRequested; QImage *image; float minValue; float maxValue; RGBClippingMethod clippingMethod; InfNaNTreatment infNaNTreatment; NegativeTreatment negativeTreatment; LumMappingMethod mappingMethod; bool fit_to_content_mode; // If true, newly loaded frames are resized to fit the window float zoom; pfs::Array2D *workArea[3]; PointerValue pointerValue; QRegion selection; public: QSize sizeHint() const; const PointerValue &getPointerValue(); void setRangeWindow( float min, float max ); const pfs::Array2D *getPrimaryChannel(); const QList getChannels(); void setVisibleChannel( const char *channelName ); const char *getVisibleChannel(); bool hasColorChannels( pfs::Frame *frame = NULL ); /* void setFitToContentMode( bool active ) { fit_to_content_mode = active; }*/ float getZoom() const { return zoom; } void setFrame( pfs::Frame *frame ); QImage *getDisplayedImage() { assert( image != NULL ); return image; } }; class PFSViewException { }; extern const char* COLOR_CHANNELS; #endif pfstools-2.2.0/src/pfsview/mac_os/0002775000701400070140000000000014105165614015576 5ustar rkm38rkm38pfstools-2.2.0/src/pfsview/mac_os/PkgInfo0000664000701400070140000000001114105165614017044 0ustar rkm38rkm38APPL???? pfstools-2.2.0/src/pfsview/mac_os/pfsview_icon_mac.icns0000664000701400070140000045315114105165614021776 0ustar rkm38rkm38icnsViis32žÿþÿþþÿþÿþÿþþýÿÿþþÿÿþþÿþýÿýÿàõêëÿþÿóãúâû€ÿæDŒ‡ÚêÿÉb±}¼¯øäÑ¤Çæ·x›ïÁÁÔí˜~ÕM©â·z³ü9lÏç€vùª¯µÁÃoåsºÎnÞ˜ÓÕ‹æšMí­ÿ­–¿¡ìcúúc×£­ áwšç­@Ä·sv¤êˆÿ‹ÏÕ·ÝÃÁk“Ñ×Ì䲤ÿ¦s£Á—Œÿé¨s±¢l”ÿÿñ÷º±Ï—ÿúÿé•ÝŒ¾ÿÿþýþ‚ÿýýÿþƒÿþýþýÿýþüýŽÿÿþÿþþÿþÿþÿþþýÿÿþþÿÿþþÿþýÿýÿàõêëÿþÿôèúáú€ÿæDŒ‡ÚêÿÂ;¦0´¶öäÑ¤Çæ·x›ñ³BV`E*ÊM©â·z³û=/Z`=9i=®µÁÃoås¹ÓXLaâã[VgX!‘Žÿªˆûï7x€ÿ w=òÿÍÖ[.d_‚ÿ^iXAw¨ÿ² ÿúùúÿµýû³FW7^úúù€ÿ vÿÿO© ÿ’; …ÿ 0ÿ„c‚ÿ ûÿúú£©üÿˆvÿ ùúùùŽ„úÿ{|„ÿùƒÿú‹ÿùÿÿù†ÿûüúÿÿúù…ÿùúùÿÿùùÿÿŸÿÿÿŸÿùùÿÿùúù…ÿùùÿÿ€ù†ÿùÿÿù‹ÿùƒÿù„ÿ |{ÿú„ùùúùÿÿù€ùùÿù‚ÿc„ÿ/ …ÿÿ€ÿÿ€ÿùúú^7W‚ÿ€ùÿœyAXi]‚ÿ]d/ƒ€ÿœÿŽ‘!Xg>Š{zz‹@hr^)‚€€ÿÿ Aff+ ,ed.9‚€ƒúú%^idwi`tue;‹‘Ž€ €y†ÿÿ XNec6\i&ÿ…ƒÿ Œ…5qA’ \7€úù€]€ù€ÿ |ÿÿ3ÿ‡…ÿ€ÿc‚ÿ ùÿùù…úÿƒ|ÿù€ùÿ„ÿùƒÿù‹ÿùÿÿù†ÿúúùÿÿùù…ÿ€ùÿÿùùÿÿŸÿl8mk ]5i?iqgùžwŠpÿ¦åþxPÑÿ¾S0âÿ‘YØþ½]I³ÿúèôúçt‘ðµ®þüñúúꑲúÉXJFÏýÿÿÿÿÿÿÿÿ’hZU×ýÿÿÿÿÿÿÿù{1ùèâÿÿõ¸ºõÿüåûúñÿÿè–r—éþüè(²þÿÿé]]êþðmj¶óÿþâMNÞÿö‰[FÈýÿ„ÿÿüë¶Áýÿvkþÿÿÿä5ËÿððþþßÈÐÿïîÿùàzyÝùÿîïÿÑÇáþþñðÿÌ8äÿÿÿÿowÿýøêüÿÿ…~ÿýÉF[‰öÿàNNãþÿô¶jmðþé]]éÿÿþ´*èüýé—r–éÿÿñúüåüÿõ»¸õÿÿâèù3{øÿÿÿÿÿÿÿý×UZh ’ÿÿÿÿÿÿÿÿýÏFJXÈû²‘êúúñüþ®µð‘tçúôæúÿ³G_½þØY‘ÿâ0S¾ÿÏNxþå¦ÿpŒwžùeqh?h5] it32#=ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿæÿ‚´ÿ‚¹ÿ±ÿK+€ªÿ‹ÿ¾™r5¡ÿ€‰ÿ@ÿÿþ¸©ÿ‰ÿŽ€ÿ» ÿˆÿº€ÿí§ÿˆÿô€ÿßžÿ†ÿ1‚ÿ ¥ÿòO†ÿ€ÿüÿ øÿÞ…ÿ¬‚ÿ.¥ÿ MôÿÿùI…ÿ í‚ÿ$žÿJÿ׃ÿ%ý‚ÿR¦ÿMÿ÷BƒÿqƒÿIžÿ‚ÿÑ‚ÿƒÿtˆÿ˜ÿ‚ÿõ=‚ÿäƒÿm‡ÿ€ÿé‚ÿÉ ‚úƒÿ–†ÿ‚—ÿë‚ÿó8€c„ÿ…ÿÿ¸ƒÿÁ€ š„ÿ·„ÿB–ÿºƒÿð2/Yƒâ„ÿ´ƒÿW­Žÿ‡„ÿ¹Bj“¼æ†ÿÙ€ÿ P×ÿ…•ÿ‰„ÿîµÞýˆÿßÿÿ<ÉÿÿÍŽÿV“ÿþŸ!€8Äÿ‹–ÿX”ÿ ñƒ%¯‚ÿÊŽÿ#•ÿûž $­ƒÿÝ–ÿ%–ÿñƒ•ûƒÿŽÿò–ÿûœ3–û„ÿY–ÿô—ÿñ¡ò„ÿñŽÿ€È¡ÿΖÿé¡ÿ{ƒÿ†ÿ€iþ¡ÿDŠÿ„ÿסÿæƒÿ€ÿ€iþ¡ÿ»‹ÿƒ×¢ÿgƒÿB€iþ¡ÿþ0Šÿ W®P׌ÿýéëþÿ؃ÿ “ÿÿ嶇Y(iþ‰ÿ û¼‹s[XqжøŒÿ§‹ÿÞÿòÄ–gC׉ÿ å•EM›è‹ÿSƒÿù„ÿ÷Ñþˆÿ Å^ S»‰ÿø!Šÿe’ÿÕOƒX܈ÿáƒÿ‘‘ÿÚR€ÿ€BˇÿöŠÿ™ÿõe„ÿ€oø‡ÿ9‚ÿÚÿ£‡ÿ‡ÿ–ŠÿŸŽÿà.€‰ÿ€8è†ÿ¾ƒÿÐÿx‹ÿ]ü…ÿúŠÿŽŒÿÔÿ߆ÿAƒÿÄ‹ÿ€ÿ€i†ÿ¡‹ÿ€|Šÿì ÿ*ò…ÿÆ‚ÿ·‰ÿ©ÿ…ÿý(ƒ†ÿ€kþ‡ÿþ?‘ÿN†ÿJ‚¨‡ÿæ ‘ÿÕ…ÿ»D"ƒÿ€[û†ÿ¢“ÿ³…ÿ걑pP. ‚˜†ÿg“ÿKˆÿý㢂a6…ÿ`…ÿ÷“ÿ&û‹ÿï΄€,…ÿÚ•ÿÀÿç†ÿT…ÿ˜•ÿ©ÿâÿT…ÿ}•ÿbŽÿ…ÿ|…ÿG–ÿVŽÿ €{…ÿ+—ÿüÿ?…ÿ£„ÿò—ÿ ÷ÿ/¢„ÿß—ÿÆ‹ÿý«…ÿË„ÿº—ÿËŠÿýª.Ê„ÿ¼—ÿ¥‰ÿûŸ%…ÿò„ÿž—ÿ­ˆÿ úž$ð„ÿ¤—ÿЇÿ÷”„ÿ …ÿƒ—ÿ†ÿ ö“‰…ÿ˜ÿq…ÿò‰ƒÿ‰ò…ÿu—ÿ……ÿ ‹“ö†ÿ˜—ÿ€…ÿ"„ÿ”÷‡ÿŽ—ÿž„ÿ ò$žúˆÿ±—ÿ™„ÿó…ÿ%Ÿû‰ÿ©—ÿ¹„ÿÌ.ªýŠÿË—ÿ²„ÿÌ…ÿ«ý‹ÿΗÿÞ„ÿ¦/ÿù —ÿì„ÿ¥…ÿ?ÿþ—ÿ*…ÿ€€ ŽÿY–ÿ@…ÿ}…ÿŽÿg•ÿ}…ÿZÿâÿ®•ÿ•…ÿV†ÿçÿÇ•ÿ×…ÿ4€„Îï‹ÿý,“ÿö…ÿ`…ÿ6a‚¢ÂãýˆÿR“ÿb†ÿš‚ .Pp‘±ê…ÿ´“ÿœ†ÿû[€ƒÿ"D»…ÿÚ‘ÿå‡ÿ«‚J†ÿP‘ÿ:ü‡ÿþk€†ÿƒ(ý…ÿ’‘ÿ§‰ÿ» ‚ÿÄ…ÿó-ÿêŠÿ|‹ÿ¡†ÿi€ÿ€{‹ÿɃÿ=†ÿâÿÔŒÿŽŠÿú…ÿû^‹ÿrþŒÿÖƒÿ¸†ÿé:€‰ÿ€+ߎÿŸŠÿ–‡ÿ‡ÿ¡ÿá%‚ÿ3‡ÿùr€ƒÿ€cóÿ™Šÿö‡ÿÏDÿÿRÙ‘ÿ—ƒÿÚˆÿß[ƒNÕ’ÿeŠÿ÷‰ÿ¹W €\ňÿþÑ÷„ÿù‚ÿK‹ÿ ëLE”ä‰ÿ×Cg–ÄòÿÞ‹ÿŸŒÿ øµ‰qX[s‹»ûŠÿ i(Y‡¶åÿÿ”ƒÿÔÿþëéýŒÿ ×P®WŠÿ*ý¢ÿk€B‚ÿb¢ÿ׃‹ÿ³¢ÿm€€ÿƒÿã¡ÿׄÿŠÿ<¢ÿo€†ÿƒÿx¡ÿê–ÿÇ¡ÿÈ€Žÿð„ÿò¡ñ—ÿó–ÿQ„ÿû–3œû–ÿòŽÿŽƒÿû•ƒñ–ÿ$–ÿ׃ÿ­$ žû•ÿ#ŽÿÊ‚ÿ ¯%„ñ”ÿW–ÿ†ÿÄ8€!Ÿþ“ÿWŽÿÍÿÿÉ<ÿÿ݈ÿýÞµî„ÿˆ•ÿ ‚ÿ×P€ÿÙ†ÿ漓jB¾„ÿŠŽÿ­Wƒÿ´„ÿâƒY/3ðƒÿ¹–ÿ€A„ÿ¶„ÿš € Ńÿ¼ÿ…ÿ’„ÿc€9ó‚ÿê—ÿ€†ÿ”ƒÿú‚ Ë‚ÿíÿ€‡ÿoƒÿä‚ÿ?ö‚ÿ˜ÿˆÿqƒÿ‚ÿЂÿžÿMƒÿqƒÿGøÿM¦ÿN‚ÿý%ƒÿÖÿRžÿ'‚ÿí …ÿ NúÿÿôM¥ÿ*‚ÿ¬…ÿ Ûÿ÷‘ÿþÿ€†ÿUó¥ÿ‚ÿ2†ÿŠžÿå€ÿôˆÿ§ÿæ€ÿºˆÿ ÿÀÿމÿ©ÿ±ýÿÿ@‰ÿ€¡ÿ8s™¾¹ÿ€*K±ÿ¹ÿ‚´ÿ‚ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿæÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿæÿ‚´ÿ‚¹ÿ±ÿ€ªÿ‹ÿ¾™r5¡ÿ€‰ÿ€fJ©ÿ‰ÿŽ€ÿ» ÿˆÿJ€f_§ÿˆÿô€ÿßžÿ : †ÿ‚f¥ÿòO†ÿ€ÿüÿ :cfY …ÿE‚f¥ÿ MôÿÿùI…ÿ í‚ÿ$žÿfVƒÿe‚f!¦ÿMÿ÷BƒÿqƒÿIžÿ ‚fT‚ÿ?ƒf.ˆÿ˜ÿ‚ÿõ=‚ÿäƒÿm‡ÿ€ÿ]‚fP‚ dƒf<†ÿ‚—ÿë‚ÿó8€c„ÿ…ÿÿJƒfM€>„fI„ÿ€–ÿºƒÿð2/Yƒâ„ÿ´ƒÿW­Žÿ6„fJ*;K\†fW€ÿ Vf5€•ÿ‰„ÿîµÞýˆÿßÿÿ<ÉÿÿÍŽÿ"”f@ €Nf8–ÿX”ÿ ñƒ%¯‚ÿÊŽÿ•fd? EƒfX–ÿ%–ÿñƒ•ûƒÿŽÿa–fd>†fd$€ƒÿ"D»…ÿÚ‘ÿå‡ÿ«ƒ€†f ‘ÿeˆf+€†ÿƒ(ý…ÿ’‘ÿ§‰ÿ» ‚ÿN…faÿ ^Šf2€‹ÿ¡†ÿi€ÿ€{‹ÿɃÿ†fZ ÿUŒf9Šÿú…ÿû^‹ÿrþŒÿÖƒÿJ†f]€‰ÿ€YŽf@Šÿ–‡ÿ‡ÿ¡ÿá%‚ÿ‡fd.€ƒÿ€(af=Šÿö‡ÿÏDÿÿRÙ‘ÿ—ƒÿWˆfY$ƒU’f(Šÿ÷‰ÿ¹W €\ňÿþÑ÷„ÿù‚ÿ‹f ^?  ;[‰fV)]‹f!ƒÿ³Šÿ(’fUƒ#XˆfZƒÿœ€ÿ’Šÿ=fb(„ÿ€,c‡f‚ÿ˜‡ÿŠÿ@ŽfZ€‰ÿ€]†fLƒÿ•‹ÿŽŠÿ9ŒfUÿ Y†fƒÿ“ÿ‹ÿ€2Šf^ ÿa…fO‚ÿ‘ÿ‘†ÿ€+‰f‘ÿ†f€”‘ÿ•ƒÿ€$d†fA“ÿH…f^G:- ‘“ÿ”…ÿ&…fc “ÿd‹f`R5•ÿ“†ÿ"…f=•ÿDfZÿŠ•ÿ“…ÿ2…f–ÿ"Žf‹—ÿ’…ÿA„fa—ÿcf‹—ÿ’…ÿQ„fJ—ÿQŠfeDŒ—ÿ’…ÿa„f?—ÿEˆfd?Œ—ÿ‘„ÿ …f4—ÿ9†fb; Ž˜ÿŽƒÿ7a…f/—ÿ5…f8—ÿŒ„ÿ ;c‡f9—ÿ?„fa’—ÿŠ…ÿ@d‰fD—ÿJ„fR”—ÿŠ…ÿ De‹fR—ÿY„fB”—ÿŠ…ÿŽf —ÿ…f3•–ÿŠ…ÿŽf)•ÿ2…f$ÿ“•ÿІÿ\fP•ÿV…f–“ÿŒ…ÿ'4AN[eˆf!“ÿ'†f>”“ÿŽƒÿK…fW‘ÿ\‡fD”‘ÿ†ÿƒe…f:‘ÿC‰fK‚ÿŽÿ’‹ÿ@†f*€ÿ€1‹fPƒÿÿ”Šÿ d…fd&€‹ÿ.fV ƒÿމÿ—Šÿ<‡f8‡ÿ@fZ‚ÿ‘ƒÿšŠÿ b‡fSÿÿ!W‘f<ƒÿ³Šÿ c‰fJ#€€%O‰fTc„fd ‚ÿ³‹ÿ@Œf cH7-#$.8KdŠf *$6I\ff;ƒÿ³Šÿe¢f+€‚ÿ³‹ÿH¢f,€€ÿƒÿ¨„ÿŠÿ¢f,€†ÿƒÿ¨–ÿP¡fP€Žÿ§–ÿ „fd<>d–faŽÿ¨–ÿVƒfE ?d•fŽÿ¨–ÿ6fN€ @”f#Žÿ‰ÿÿ›•ÿ€4fV €ÿW†f\K;*L„f7Žÿ†ƒÿ™–ÿ€„ÿI„f>€OƒfKÿƒ…ÿ™—ÿ‚†ÿ;ƒfd ‚Q‚f_ÿ€‡ÿ‰‚ÿˆ˜ÿˆÿ-ƒf?‚ÿS‚f žÿˆƒÿ‡¦ÿ‚feƒÿVf!žÿ‡…ÿ‡¥ÿ‚fE…ÿ  Xfc:ÿ‡†ÿ†¥ÿ‚f†ÿ 7 žÿ†ˆÿƒ§ÿ\€fJˆÿ ÿ†‰ÿ€©ÿGeff‰ÿ€¡ÿ…¹ÿ€±ÿ…¹ÿ‚´ÿ‚ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿæÿt8mk@oQ*ôè¾—pJSÿÿÿýßµ ÿÿýýÿÿ3Ëüÿÿÿÿÿ+S@ îþÿÿÿÿÿVKÏBÿÿÿÿÿÿÿNLÐÿö;pÿÿÿÿÿÿÿxAÈÿþÿ‚»üÿÿÿÿÿÿqDÈÿüÿüó5åýÿÿÿÿÿüš¾ÿüÿÿÿÿw2ÿÿÿÿÿÿÿþ”ÿýÿÿÿÿüï.aÿÿÿÿÿÿÿý¼ÿÿÿÿÿÿÿÿl©ýÿÿÿÿÿÿü·æÿÿÿÿÿÿüì(Úýÿÿÿÿÿÿÿàäÿÿÿÿÿÿþþb#ýÿÿÿÿÿÿÿÿÛ²üÿÿÿÿÿÿýç#Rÿÿÿÿÿÿÿÿÿü'¯üÿÿÿÿÿÿþþX˜þÿÿÿÿÿÿÿÿú—$€ÿÿÿÿÿÿÿÿýâ%OxÛüÿÿÿÿÿÿÿÿÿ!ý¬~ÿÿÿÿÿÿÿÿýünp™Ãîÿÿÿÿÿÿÿÿÿÿÿ‹úÿè'NÿÿÿÿÿÿÿÿÿýñùÿÿÿýÿÿÿÿÿÿÿÿÿÿC…øþÿÿ±Mÿÿÿÿÿÿÿÿÿýÿÿýýÿÿÿÿÿÿÿÿÿÿÿÿ^sñÿÿÿýê)ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÖUlíÿÿÿÿÿÿµÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýÿ½<\ãÿþÿÿÿÿüì,ìÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýÿÖVSÜÿýÿÿÿÿÿÿÿ¶îÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýÿ½aÐÿýÿÿÿÿÿÿÿÿŸüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýÿïÿüÿÿÿÿÿÿÿÿþ¡ºüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýÿýÿÿÿÿÿÿÿÿÿÿT†ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿúŒþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýË¢ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ2ðýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ?–ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþòoA2ðüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýµLûÖ¥wH–ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿx˜ÿÿÿå´†W(2ðüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþ,Îýþüÿÿÿí¼¥ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýç ûÿÿÿÿüÿÿÿøöüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþžMÿÿÿÿÿÿÿÿüþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcœþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüþÿÿþüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿùÐüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüÿÿýçäúÿÿýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüØ#üÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿÿæ©rt¬íÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¢Pÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿô¨[N ñÿþÿÿÿÿÿÿÿÿÿÿÿÿüÏ‘ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþö‡1>–úþÿÿÿÿÿÿÿÿÿÿÿÿþî Gøýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþø† õþÿÿÿÿÿÿÿÿÿÿÿÿÿTÎþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýþ› ¦ÿýÿÿÿÿÿÿÿÿÿÿÿÿ}=òüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ̹ÿÿÿÿÿÿÿÿÿÿÿÿýÙ ÁÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýöTbúüÿÿÿÿÿÿÿÿÿÿþñ1íýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¥ÿÿÿÿÿÿÿÿÿÿÿÿÿ_²ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüï.7õýÿÿÿÿÿÿÿÿÿÿÿ‘&äýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¬˜ÿÿÿÿÿÿÿÿÿÿÿýï°‘sT5¤ÿÿÿÿÿÿÿÿÿÿÿÿÿÿýú>Nþþÿÿÿÿÿÿÿÿÿÿÿÿûᾞ~^>8ÛþÿÿÿÿÿÿÿÿÿÿÿÿýÎ ºþÿÿÿÿÿÿÿÿÿÿÿýþÿÿÿÿøÚ¸”ÿÿÿÿÿÿÿÿÿÿÿÿÿÿi€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþýÿÿÿÿ2Ðþÿÿÿÿÿÿÿÿÿÿÿý+øÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿPƒÿÿÿÿÿÿÿÿÿÿÿüÑâýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿKáþÿÿÿÿÿÿÿÿÿþš‡ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ{ëÿÿÿÿÿÿÿÿÿÿÿEZÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿvèÿÿÿÿÿÿÿÿÿÿþùÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿü¦ÿÿÿÿÿÿÿÿÿÿþàóÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿý¡ ÿÿÿÿÿÿÿÿÿÿý˯ýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÔ5ÿÿÿÿÿÿÿÿÿÿþ¢ýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÍ4ÿÿÿÿÿÿÿÿÿÿÿ{]ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþú\ÿÿÿÿÿÿÿÿÿÿÿHZÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýÿá[ÿÿÿÿÿÿÿÿÿÿÿJ2ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýÿånŒÿÿÿÿÿÿÿÿÿÿÿ&;ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýÿänPÑÿÿÿÿÿÿÿÿÿÿÿ/ÿÿÿÿÿÿÿÿÿÿÿÿÿÿüÿÜcE¿ÿýÿÿÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿýÿÛcYÑÿüÿÿÿÿÿÿÿÿÿÿÿýÿÿÿÿÿÿÿÿÿÿÿüÿÒWSÎÿüÿÿÿÿÿÿÿÿÿÿÿýÿÿÿÿÿÿÿÿÿÿÿýÿÑYcÛÿüÿÿÿÿÿÿÿÿÿÿÿÿÿ" ÿÿÿÿÿÿÿÿÿÿýÿÆKaÚÿüÿÿÿÿÿÿÿÿÿÿÿÿÿÿ,ÿÿÿÿÿÿÿÿÿÿÿÑPnäÿýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ>!ÿÿÿÿÿÿÿÿÿÿÿoäÿýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ2Dÿÿÿÿÿÿÿÿÿÿÿ[áÿýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ`@ÿÿÿÿÿÿÿÿÿÿÿ]úþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿduÿÿÿÿÿÿÿÿÿÿÿ3Íþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿý§Šþÿÿÿÿÿÿÿÿÿÿ5ÔÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüµÊýÿÿÿÿÿÿÿÿÿÿ ¡ýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿóßþÿÿÿÿÿÿÿÿÿÿ ¦üÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿúÿÿÿÿÿÿÿÿÿÿÿçvÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ]Eÿÿÿÿÿÿÿÿÿÿÿì{ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ™þÿÿÿÿÿÿÿÿÿþâKÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýäÑüÿÿÿÿÿÿÿÿÿÿÿŠPÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿö)ýÿÿÿÿÿÿÿÿÿÿÿþÕ6ÿÿÿÿýþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿiÿÿÿÿÿÿÿÿÿÿÿÿÿÿ™¸Úøÿÿÿÿþýÿÿÿÿÿÿÿÿÿÿÿþ» Ïýÿÿÿÿÿÿÿÿÿÿÿÿþß<>^~ž¾áûÿÿÿÿÿÿÿÿÿÿÿÿÿþS?ûþÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¨5Ts‘°ïýÿÿÿÿÿÿÿÿÿÿÿ•§ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýè+ÿÿÿÿÿÿÿÿÿÿÿý÷=.ïýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¶ _ÿÿÿÿÿÿÿÿÿÿÿÿÿ–£ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüï6ïþÿÿÿÿÿÿÿÿÿÿýúcQõüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÂÙýÿÿÿÿÿÿÿÿÿÿÿÿÁËÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüõBvÿÿÿÿÿÿÿÿÿÿÿÿþÿ¤•þýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÎTÿÿÿÿÿÿÿÿÿÿÿÿÿþô{ †øýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüúJ éýÿÿÿÿÿÿÿÿÿÿÿÿþú‘85‰õþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ”Ïüÿÿÿÿÿÿÿÿÿÿÿÿþÿï¡S\¬öÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿW›ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿë¬tr©æÿÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿý(ØýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýÿÿüåèýÿÿüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüÔ÷ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüþÿÿþüþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþ¡cÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþüÿÿÿÿÿÿÿÿQ™þÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüöøÿÿÿüÿÿÿÿü" çýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¦¼íÿÿÿüþýÐ'ýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüð2(V…´äÿÿÿ™xÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ™Hw¥ÖûL®ýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüð2?nòþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ›9ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýð2ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ©ÃüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþŒùÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿŽMÿÿÿÿÿÿÿÿÿÿýÿýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿü¹¡þÿÿÿÿÿÿÿÿüÿïÿýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿü¿ÁÿÿÿÿÿÿÿÿýÿÐa¼ÿýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿíµÿÿÿÿÿÿÿýÿÜSVÖÿýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿñ,êüÿÿÿÿþÿã]<½ÿýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ³ÿÿÿÿÿÿílUÖÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ!(èýÿÿÿñu^ÿÿÿÿÿÿÿÿÿÿÿÿþýÿÿýÿÿÿÿÿÿÿÿÿL¬ÿÿþø…CÿÿÿÿÿÿÿÿÿÿýÿÿÿúòýÿÿÿÿÿÿÿÿÿR$åÿúŽÿÿÿÿÿÿÿÿÿÿÿîÙorýþÿÿÿÿÿÿÿÿ}§ý ÿÿÿÿÿÿÿÿÿüÛxO%ãýÿÿÿÿÿÿÿÿƒ ˜!úÿÿÿÿÿÿÿÿþ˜^þþÿÿÿÿÿÿü®&ûÿÿÿÿÿÿÿÿÿR#èýÿÿÿÿÿÿû´Ûÿÿÿÿÿÿÿÿý#gÿþÿÿÿÿÿÿâÝÿÿÿÿÿÿÿýÚ(ìýÿÿÿÿÿÿç·üÿÿÿÿÿÿý©pÿÿÿÿÿÿÿÿ·ýÿÿÿÿÿÿÿa.ðýÿÿÿÿýÿ”ýÿÿÿÿÿÿÿ2zÿÿÿÿýÿÅ”ýÿÿÿÿÿýæ5óýÿýÿÌHqÿÿÿÿÿÿý»„ÿþÿËGrÿÿÿÿÿÿÿp;öÿÑNNÿÿÿÿÿÿÿBŽÏMOÿÿÿÿÿþï @S+ÿÿÿÿÿýÌ,ÿÿýýÿÿ´ßýÿÿÿSJp—¾èô*Qoic08 jP ‡ ftypjp2 jp2 Ojp2hihdrcolr"cdefjp2cÿOÿQ2ÿR ÿ\ PXX`XX`XX`XXXPPXÿdKakadu-v5.2.1ÿ œÕÿ“ÏÁ ‚{‡²ÓÿÙd\\¦"'SuzðÚ˜­ÁZ(Ü{@ D_ Üèå^D˜Ú´C¬©WЕ“3ЛU=”Æ™¦à;£á@ÏÀÐ$Ê ‹L*ØÌúä3’þm˘ßàñ†Œ‘@‹LeE^ÖI£ ØèÓJVLBFð+Ï–M”%8ÏÀ€.[]»u¯bí§ß9ƒí÷¸v<ÛÇÿ#ÞÄû±·,²1ßš`EyÏ0»ø1uOŸRÃRÔC±ÕñÇ£wжþ$“0QÀãµb%侨S‡p›­Ae E£ù¸þϺ&„g‰1òb”,ð'å¢TUÇà´?À-×]Œ¿ÆòÕÉ#¨—'³•”_\¸BLCÏy§Bøïðg‡÷Ìÿx…ýu,öTn¨°vÇ|ÆU &qò™ ö·_r;);¡Æ^M««ê·Ž¾% /ì]L®þŸØ£²0ä¼]•sàî*ä¾€nm.<{P×f.|€­œ ©Ø‹ñ=R…͝ Q—hšzF†IÕû‹a¶OBœy²‡†qïûè{ÑÉb[Ä÷Ö‘lzðû»¨^©Çàm´º¨AÈwÕD9¼*ù+C²e¾vÕlÄX‘ìÚ"M5ð#ï§ÎFD~ëâ%_¸2Ê‚ Ys(êZY̘Íã9ü½ZE™†¨úÄæeÑ©qÿ7¯ÌS•B訄LæÛ©Ñ§°«ª"é¦Z’<¿]í52•’a¿ðSÄŒ`#€³ a[?O”ðKÛ 9A±…øA³ çÀÛ¼ù”&íÇàI€ô? 9!U«k$y¿ˆÙªuüÆ6 l‹=%í<¯šÊ±t,÷¸zé¤ "Z³Y§åÚÌ–$?YÖ…ÞSë½÷|×C2L¤ê%V¯Jæœ@„ÀÀ/Îzj’¨¡ð˜Ný¥äÅÏÍ?ˆüÏÀ9Qæ&¿~îØM’#ß"'r\Ý:ÄBDb§?fv p0>;2t?DbØØX®R鳤öN ™ÞîovÀ—LCZº•˜¹ŠPƒUš‹ èÙÕ[…ê›ðûµ«-ö${´òélnuѰ?/zf·â¬¦öBm®Ó#—Üüòá“ …$y‰û»…ˆ;Ò®³l`aÆj¯ã¿Œö EÛ8hB—Ž ø/Uêä½0%F ÌÝ*RéC™Ïδ³λƒ?L©¦JŸëþÄØÜUZîûÄó*Τ±åü‚çÛ°Û»ÞOüüàyo³ŸàÍ/R4üAqÝ‹`‰ †1f'.Í]–]'ÌI|'V;c ¨7oËÀåR»@Lâhðñº÷"ï OøH$<’É'8BŒXµ—u{iÂbù/6± ñ¹XÎCrNsئ#]y)DFØ0î+º-èÞÿû™Ófd\¥yÑž²e¥£ìIñ¤m˜ ÍT{‰Ù’`rƒBˆYÍÖ¹Ï‹×Æ¹w‡ÔøWÚãBjÈ¢,r“´•_…w¶1¼(´ Nî³`‘PŽe¹uÆ*Á•ÿgc¨ß÷Öúê‹®»0]ù„ô„ÝøºéãÚM6ááA4ÖV¨9ÏZ$ º\¾z9j(x }‚Iä¤O?A(M€6¹Þ›©Î’ ,j1Ð8ï\ó¡GÉ+Úe @Â(ÅL”ÞU ÀU«›¤0Dëƒ®í¦¾n>Ï©#šÄ³>DßLÃS¹Õâ{88ÇT &VÅvÓë–B^ËǦ`lÍœ k¾„‹J¦T:üJ®è¯!RÏØœ+ü*%ÓK›9•ÑîE?’§ÒâhéóðôŽô%ß·O™ê[Ýþfq\T³íS»sØob§9”O·Ä øŽ—ªiï׿ÇÇÀg°|k4-ÇÊ;!ñK=éj›°¸3@ÇáMGáJCð§m1õaÞ´ýÑÌ£®¨tÅ÷Õ s©ìkVd#v ážØ:Óâ•\?ËÝå ÚU x¡ƒD&Õ ©ô–Éó• •i"˜¼V-i2²ÙK€*µ‘Q¯Q&ëõeÀŠ"J­:ÌÆ[ÇXë æØh|‰ÜQPŸÿp/lå»Z€Fíˆäyò‘æQ©G>gïN/õû0 òɹj¼¹G/hC÷>Û6Zž*KÂ`ö¨Åèæ18¯ä²›/¦ÃÙÔ•Sš¯QKB S5ÂëìõOÀê?÷NËã KaéaF–š€uPž¾FíÎûÄþ6EçµL†3>l5KkÁ 8b(¸TÂrZ íM‹´ì¿Ç Ãp¤„´¥mâ€æmIn¢#¬ïõGyÒ€‹µ¸„0"¥YçÛ5|Ây›îÁø\Ò~¯$…þúφÚ-±Gÿ&î\ŸiˆX!¢Ð#„ÊLå‚CxÑžô¾ªWi=kYàd !MAƃßËþÈä“„‘Þi–ñTÌWúЫâÎyp ØaSe¨N-·lÑÒ)U§˜ß|5á\hk/ЧGh*BÖ©Á_(îÅéÃíYÁN‚Ø„^œÍ"ú{O¾Ê^ý1èø‚ÞnS:–O™öÀ4@ŠùŠŸìž¿töY ÿéUm>N7æ¢û^@öÃðq Eד‚Š7&Å‘¡XtÈ¥bð ä§»”ýK²ü{ã ާv3)„·ˆ¦-}ž‚(ö°Þø?“ÅÌãëMu”ƒÑÏ8‰6Xzc4ñ,¯m¹‹~³¢vˆcP¹ÝkZõ±SµÅY paòy›û`^E0ñQlé¥Ì(êÑóê½7ÎXÙ¯aÒËßööOëm(ÿ6‰a‚KšXgCºc>¢_˜ìÓ7ƒu£; ÿ€ï}ºi“¾_Yöû·>.ç`;%õ.ÇáYGáVGç[€m¶ñ"αn[§ gƒ¥ +•G dkùi» l£ËÙ.1åÔvF.¨¡\23eË b­Dç¶ŸDëÊé:­‰gÚ¨¼ÓYЩUNÕÀd—c çÚß’hPŧß$Kþøóf´—d¥v"\R€@ÝÌO.ôŸK=7"¸ly"bÿñ*DžÍ&½Ö›·sSþ•fìÒƒ…ŸÜ~n†-jžé†±.“º¨œ2ëÍ/ ~ŠU-ö ¹Ì«M‚N"Ès‡GNDÔ™wš`†Wv}˜ëª0ì¼Â3xžü¶Î1A’–‘YDŒ7Äóa½4æ°k´ÑË‘ˆŸÒ¯(þ'Ä òúnN‹&*\»eõ¹çr3ô^ÚÃ×”sEáÖoÚD¢Â8}úf†ÈÑîhɃÝðö£Ñk)òA®0•ßóAWŠ ôb%õõ¨ËÖ‘Hû_ °óþùÕìdvSœxjÃÆzêja N¶è W",éÆú³”ÐAÀA×ÒùÕ¸±™gæ›øöG(ÏYTׂ‰ÝÞ8K•Y*ú¯(MøY°¹qùm&¤äc蜞c}2)lšÂšªÐ¸@Û*nõ­)ÅÒ®ÇÉ4pxo"ž[&„Á†b'.¥•'Lw¹…C™oäË\?n§="‰ÑHå“&xe‹Mß7 …©"úú¾¹P Æ­ú‹ÏÏ¡9ùô#ŸDà²áUÀ6ŽR¯ÑÍ»Éêß¡ u¯…ÇLÜæC~H“øª%ÌX‚Gª$÷ï²yRÌtóçb3{½´“–m…IgЍêCZ-­P%IÈdW×` N€[¨ä¹û¶BHÛ³¸Õ¿Y¬ööR䯔YÚ¾ÒIS;8±Ñ¡,á™@AaÌÝ»·Kú «QE¹fg¿º{Ø/Šß'ì<,™uK„Îý¸ÿuQSÒbðäm÷BTe7EÀ¥¤œ}4ÙšUi¾wüÛK9º7pÒ;Ñ–´ð¸©•ƒÞ–ºÍº]¹¬¬¹ò'°[Ú}¦¸JÃDª.‰²©f%¶`d(“ Oª€Ê/³ÿO@ú × ZU3±í‚<û½þ8e¶!®òœ[f9"!¬`å¢}i6+‚z¡}8Z¬T•²©²ê庪m¸»"瀞¹C?¬‡øÇàɪyhÛ´€npÞËOÈëÕfÄ5ÅÏü÷¢ËAã:78Œ*¶ìPøcXî`BlXŠ›šøÐßñ2µþÀÅdLE'AHH»Éz66^4ý“æP œGÅs]ÊÇ•Tÿ/Êî jî›Àý‹“ƒúuÌT£ëóv SmÍ%'6b (ÜÄ<ÿ$4ÿI´N8p®}i"먦d]CÍ›ŸcQVUìmn^+À…CòeŽ(á*0Ei¢©*ª˜Ktã%v íV n³³ðÈT"’}>÷34™Ú·,“œMŠŒŒâ$ ì(¾¶b‹ù†*èÝ܉ðá³ÕE@ß›Û Atñ=rNÐ¥KfàZ ÀŸgH¿\1Æè¨ýUvrÛJ$ЫkŠDƒË„çb!»hò©mŒðíÔЭ•µO„=^Ÿß1öµƒò.?dÎßÌA¥@ã?&¸amTóS;tëu5«]5µ¯VòÞlî€;j]ò0º•ìeË×Ò®Ùd¨Î§Ì­^ Ö|®EY„AyÕÂ)<–W™^õ`Øòœo}ÅÙÞü‹º€ã’’CÓ6wSs—øÛ:Ò-+)ú¼>5!…éÔ¡#ÖœÅ÷qmB¯€û,, ²$W—lá@R‡e‘±]´Ãµ-Üj˜oîo~îPÔ·=‡9³ZíĤ¤¬TàÂdóuuZþ˜è3 §Ä¶8}ÀÄ5°68ŽVöîF=’±]ºâÛ ¥ªIP´ËÀV׉£¢iW|:}Ó.íì1'ÈÔ¥74†º¶®ÒðufÚ9®Ãü  Ý3«ÆÞXü¶ špcu­ràŸÃóÊ K0塬âèŒYof•‚ÿP‰rzbØ x; îËÆ•ªß‹µ)uTÞ|“ˆ2‡þ eþx°;«àkJT¥†RÑh£UÙu üd.[õ ­ÄfÁe$;WZkíôšž§`À©§òÁ±KÉ2 Ü=p<º<Èt¾µQ+ \ÆUÊ)1®ŠùI︺å»Þ2âÝ&Ÿ Á­Mw¡òüþÒmiî̹¡ÿL.8ZªÏiÖ±I«òi<ÓÏx•=’§5þÖ\V¸)ê–@Kk0óU¦ú“vv-kͦ^Ûçl~éÕ¬m•ñrÏF+N}ÂyB0“TžLŒ“Ò+ %j»(³¬“…~8Ü¢%"¨Àw¿_9HRÑë`O-`&L‚,zóްî_Ù⢦Kðþó`6T¹BÍÜå¯\wùR‘Ö¯¾k£ Æ¢ %lÊËAÔ øMZnåƒ “ݪ¹Vd‚"í´u±á„aŠC_×£$>Ab/ªp|Ö$èb0‡ƒýbQ¸ièì$ÚWGÖ!€M£e\øLöÇÓ¼H–=*ÕÉPo6þ–péê¿hÏøgï,ëÇéhÆ|z¿êd{›7à(ÎAÏË2"g.ËÀ°>À.…w xèM5mé¾íTÜ’B…øNòæK¯‰­›ÍŒ->“xð´Õ°ò`I)Ï9@=ïÈk²¥P4‹oEÿp 6ÊWÞ*¶´cÆ—ï‘ Wø¤ojd²[1¡-¶`±J¼0Z»Ü4ãbí-I´Y1”BÂF{ ê߃’e»0úáQVÓ·›ëßrV“ÂÖÜ¢úœ4Àe”jQìÂ-‰ÃíØx}»Ãc²ö‚àüÔe‚qø?æì§Öxøæq¯ùª ìÿƒ±€ °§zf¾ *>lŠ.wy6¨J ͵âLÆÛ°Œ Ä)ýœ¤‹iûϺh>Z)æ3J óF²#@¶›Óîp ëÓ€uK³‡L:CÖm°¾Ö†‘fô{Ë]ƒÉJìóKEžÆL‚½å…{1xo¡žÀçõÅmH;Òqr²è)Ä>©óS2y $'B1Ã9 ‘ß‚ºá<^š„ß±ˆ)ñ*ŠÖ‘ª‡…¦£¯ñûàè×Ý~5´¥ü:¯ºïuxÝ­Ÿpì7/ȺËwð¨fÜdÉ i³e¢Ôôj ¡¬¦)Îöõ¹b~^tÎ3Jxnã*Q“FØ™·Ýp¹Ç­:mÛŒÑN¤ ·â‚ár.ÉÓË< “—ËV 'ïõÐ` ÝèÝFL¸Â{1;©€;B/mMnbÄ·e\Çóô› Àe0sÓíóÀ½2öÒ53+iEïh³ÜP‚ - ª{_ŸIÃ1_»­ Ÿ!üî¸iú«·}vS Ýù-/$ù nª÷°ª Èx¨·ÓÄÝžãäg•1÷Ï¼öѽW¼½ß½³¶5€øÖë.!\PªíâOk)! :¬'i³6¾ä4£ò½ì ¿`Võ¾"¹Í}¼PpÜL奪ê(E|€È“å?,êS \†ùVílšés›àT¢fÍìÀ¡,Ð÷|Œ¿ ©Bc°LË-0£cL桳³·‡Ê7çÈ’ÓL$´êìTŠkG”Ûƒ¥–,6×?8׉°âÀxÀR>ú(®Xˆ!O5wÇ:@žôhÛ~ëýÜ=„z{¾öÏ r´É™dYB¬À bùµº¶<"†z·5ng#³¯.€Ø–%W¾å{åK0RmxkŽõZ¢z…<¶;ÿA¦ö´LÏÿ4Hs °ŒŸëlª³èÉÆ%Ýž­0ËYWùWÖ”>ª Mßæ°ŠªÄZ8˜ê_Ç~JÖn!¡û!Ë‘·vÑ…ÆUî1X©Î@»•B@Ó§ÖI:ßcH[ù}hùòv•„!±!œ æ;jw«ú¾±C)·WÎÿ'÷°à-àV%5ŠxÐ?Ü+'*è¸ÚNo ¡­Åzå•xßRQ¶@W>Ñ´ëîvQŠæA?2#l Úg„¾¾F í¦¦Q¡ê¬¤)#n9N.'ä®IR>PêØ gû@y;dôË…w&‚ûÝd¹5¥ri{9sÀþ-j)n´wM€›¿™ÓÀ¹Ã'9%sr„4›eÏ-<™dY%P“ËöÕÉIò,À!Z&$6ÆiãŠ~ì­;ù–Q?ܦ'þÈìÚÿZ¹ãÃ}ÙšÓ-«4½deËA/1ùþA¢+«ÏÖí%n4ÓsP ^©!6ýIõVLš=oÆGè@»ÏR‰wï|»%îŒ5ÉÄ€iЊÏ|Ë=/YÚ¡ñû,Ð]ÉÝ#»{­Â­ªîû0ÅÈÅÙ}®±“¬íåi*Ãí¶aöÚðü.À¹³ßH 'W ~S²±Z¨åõ jÏ’zLÓ—§~G„ÏÄ,Y qôVJ ¨~ˆý”1—ô³‡ *0摳Õ|YMël- M ïX—‰µ¦ŒG¼¶"VèàÝóvyG¥7þÇj-ß4Ÿ8—jCž2~dJšÿ &p¶UÅÏ ør3ØÅé× ‚Ô$hz‘/ž1E’ÅCÊÂbJ‰¡W¶uúéì@ÒŽÀ'ª@MLÙ, (dœA°ƒŽÞÁšÏÛ ·.V;µ|‹q'i“>#B?ÐN“¨³óûÙNH§-…aaJ:ËÒ;;PéJ!Ó§bÈÅ–pàÒkßLj ¼²½*ðýÑ%—Ôþt¢4ÐgEù£€žä÷þŸ®ß“xŸ²@HqOÌ3OüÒîÞÇv­ÔÔ§w “zF¡;},Z¼Ó¸(ÑàI¡K¯W˵‰¶=àÄ‘û¦Xd3\| Ûô)Qœ@Σ–½ÒµT=gúþÓùßg@NØ2,÷cÊ`$Ϙ|«žêI…ÅýÓ_rÂwi YYwO Ñ"–YO/¼M†q.]'S'Ô_„Éì’››#ªvü zËš.^îód=v[ƒˆ@OÉ‹©?fMùׂÔwíc…}ó%àf¦I5EŠá$ŸOV ÀñQ @öP¡[ííPâÆÀ÷V¨ú’ÞŽ‡dÄ)«Q;sâ¨ãF›ÐöJ¦gRŸ Íl®DÉuXƒPµR(˜ìþ¾ju]e®Ê´^­ÔOý–|'¦i/K ~ ’•|ƒª¢.úë¶~­ w=Mvy?ù™Ã÷bñÓ øÀg²VÍá™Þ˜øÂ.þ»iÄŠñB8iÀ8¨ ãF—8l¬$tbóO9DÝsVÏÏv§ç»ðü7ˆÔÿ%má,†ÄW˜6€\lHýΛëwÖŽ‹ºýýõJb .LQµKã‚:M# lÆ„\‹­qVV¢i–0j8±!dòcÓ÷UéŸ}Ãn¸=ÒL ž–Ê…ÿÉ'o¹9"¾¨›„t´Ñ,ž®Ž¢3¼?£ÅϹÜÅ—ç8$…´È ì/,Ô1p§V¶uð2l+Ý f #“ûÞz<‘‡}s «­21þd;ÚrþÈ2NtÉšÀ™;  ‚©ûŒÇ*O¬ïÕåÛ•SÉ/0svµŸJE¬‡~Tƒ‡®„ÕMhïæ_-ûôlŠQð×Ö„TØ-pè¾õ J@dÛ½Dl !8PÓôV~U®×õ7p<Ià¿TÑì…kXÔÇõÄ5 &Ûk»D6—AÏ,¼½–—¬Y—&D¹@éG«œ8€k»õeé+߈ŠíÚa‰6¿!ñÙ–™xîüÕצ‡6åÔn‡…ôØåÌ“Ÿkaâ®fZœ¯Ô<.}‹£Óð+ÕiÙc=Š<X˜ß¿WRèãšP¹u“ÛÐ%9dê7bÓ½s¦ä‘u $vj˜¹Nµ5ÑeÜï&{%ÿF¨®:·ojü½ú–ÌZVy<ÈPkÌì˜9%5…‚VŒQ§‚ÿ÷×ÏcÚâÈŒç÷—.9>ÀÀ±l ôG*+(ý{fOi<»y³SÓdžUû‹de}  €”ûá,²š¿Ê–W}—lq.CÕàÄá&FÔÊø3¿mÝ;,n‚4-¸Yg¤¯§8;¢ç7 »‡}š/_ 0CÛ£´™hËAzÆŽ]„&0ø9¾²Ǥ^‡nÞ-AMO¾?èavÚ2 þt²Šj —ÀD­‹Ê’E·åU¿c·ÐÆ^t ¸ï¦ž²ÈY3½ÜÁA&é !o!©¹™ÓÊô”€ ¶å“ñj½ÁÁÆë;Ø„¨GccÞϼA¥y.'Ç7¯i¯7s¹öI½±Ôȃ¼¥LN³Ã`³¥‰rßÓuï÷ïþÚ¡keXå‹Yêfk1P»·É/‹A‚íd©'iØžt//Ëd§ˆã¸O„,) A6롊lÞ0‰ÁNÎ9{¡C÷×4{úQhÑ{î-Ò…^T‹zô*ñ^ á0{Ù‹–‹Ѿáâ*â…ó©'´zvBu ºwœH]þˆ˜*Ó=6úðòŸþåáp¸|2ä-ß홵êó˜Çβ‚Sr·³è<Šåè܇@ŒCÕbŽ$¨ïãáÔßãKRö±ÑaZ×8³òû ]f‹R Ba+”eÝ­ŽC9êÁeECLøo˜9;FèÃO˜ÍÄ2v®ô$#¨IE»{vï6`½ŸCžW õXU¬æ4çÍIF(ÌhõD.ÁÌ}óøó±ݩռùùáÖw#¢O0/6YvM}'Y@Ÿñî,ÒlVA9j òM´RßÌÛ¸O,œE¤u¤î¬‰ ›ïK¾(ÂÜ›õá:l&WÓÎ~¶AfóH‹QCÙQ{ñ<¨þ×:}*IªfîUÃjÉf¦Lüdwôu&ž–¢"S®ÅwñèÂ0¾—}ä‰l ÷¿   °YÒgÕ|gø“¸âýQ}†ñ]Â9 “ò˜‘ts>B,»"|Å#ó©Àå½øÖùB¿6•ùïõ7ân»Wþ¯tkëøIfÊöç‡ÃNùk—°¡[®cØï jç ñ“¦úؼµI0I²,3ÒÕ#v×DÌ=®Êi…s†Z¶QiD³bBŒÌR]™Êï•W‹#M¶—v±cŠ…å¢Ã5iš—¾$ ¥ÜÞ”¢Ž3íZÎ"«w]fLù~à£M¯i‘°ÆÃkœ$9ü–ßÔ‡hÙ3?±¶)Tê l¨„…„ñ‘jGÇáꇧÜüý0@äwðƒ 3ÅL)B¤À)®ÞíÞÆuÖ½ø½ý%aû=ßzœù;rÞþûŽrC†çŒõ4:)ɵc-~ðØ(Ç=ˆ·×ÆÊd œ˜Ä§Jgn¼ â3Çæ}®4 TPbX'À‚žuѱ©ü›¥Nlåê s×S—¢¡ƒ/'`H××F•Qö¦ÊÖ#-ôe7y›;gzBB5ýÇ®1”F;ùÊŽÑ[÷Ùx¯L¶=O|ø¥Œ°Õýº çd1°wá%Â'ä˜]PšÔ•ÝЮF¿Ñ†«4U|ïQÙÔ`)³²ÝRi_õ6ÚËž4é­¼Ã(†@´/ýzA¤ò†?´–$Qg}÷Ýc}Ëy;Ø-­àÄ< Æ›Ò.’I%H{ΈïàVšX ¨œÍ&ô[Q§¨r ÛÆP…Ã߃cQ‰qõÛ݈kvšÈå›”CrdK«ÿLØØ²-å[j-µ¬%ó+ÒùCw2fÖÁiéÛÌ´Uß\9K£î¾?å,rÂ`·Êß?GDEò~âþ5§Ž'hñ÷ dû¿ª¶&É&|ªø»,£æ ®alM‘£?jâ—à’+DëvõYÐ M ¢L!ëÅCâ†B_[Þò+µ‰ãÕÓ›eêÖwö¢·²Î|£´;ûs×±*1«g¸{`O4%|!½9óÈç4òÏî»c]E%i†+ÍŽïÖwX¿ÿ*7r ŸxQ‘áY—ýÈÕø‡ÿFE$óÛ)œ¶ë~x¥½%òÈ·|S°É6+긗7Qy£=n6c*µ&Å1]»k“ ÛÁ.âË/pËä³7ÈTBNVLQÊÿf:¬ÙPH½i–}þ‡z!Ž!+¸LÉš3+ öVoŠ“m6"N ﱘ I½wû08Ð7@tð!.¹»ŸZ…©R–„Zm›wC¸ýöÝ{é»sÙ xž;#óvn‘'ý’€ÉÇ3ÝÕß›Jb÷«f\O͹gÓý)‚N1(ÓL,x’J‹„žAÍ!!ái± d±’›BlKßKkï ¿&Œ³*/ë¤OùCsêÙ‹œFÖz¼Õ!0SLrÆ}Œƒ8-'Ò-äø3ª2såv y»6jµP¬–Y:†&íçѽèC.ñ7UHÂ9"¸h×ÏÔ%q£íÁðL)ÀÒèCñó–æ¹¾xPËÔ×í`ÅeR6#µ;Ò{[5½*Äȹïõ³Mx› ]y[z`9tlaÅÐö‘]üG!h!% ,9µÏ®Â¬µHæý‚$ÃåcШ­“Šy¼ÜoôðÕl§Ëìì”u fâÈÂ7G-˜8‘³§-h©ÏEÁ°&ÆCLœLˆ ¾§ËánU K4 ðôÇ}ÊÙRƒ¶Ý[I0Û×ㆠ™µœÓþ¼½$”^e¾ÝÃò]D£}¨¥É7عýÜø‘ÁÇuPB)à³ÐIë´W¤²8¾NÎË#45õö¢s=P°e‚ê°"vݺ+c©þ” ÿuŒµJÉ©AÎ`Ÿ¤¶Å:ÁõIæ6£YaMD):tºqÍ;‡æÐó:6^ãש±&ÅŠ¤µ‘)ÿY}ºÞú•q.‡ ±¸ãP!ÃcÞH íV$¼Í³\r» ÒBsAj\Û=ñÏN#j“©zcر¡‘Œ,·‹íGÜBOÁ¡_ÿt¾=D€œ½Î[úq |Ÿ{kFIÏ…­YrÜP¯ âÿL§‚‹ÿÕÅ$?Gz2p±îltM#9WUÙ ??WO€!¨Re¹³Îw.7{Þƒ‰Sël½q}k•¤D¥‰Ôã#SäE„É1}O}P^O¡ ^.Èè›Ø»`Úºœ×\Gµ˜‘h¸S^G‚…îs'¨Wø°WÏîË j›Í9·®<=ºð4eûÀ0,Ü5Ï«¹Æ4W™eµ¸ ->*/Ü•ûŽ«I$j*Ÿ¡¾— ¥ãéØ;è$tËwû I+X3¶ùÚÕT™eY ö`P sVšþ,nᓹ}ŠDŠLš —e”·” 9yS,zÞ7ð»ÐŽ šæ.„nK(Y{9À—©x(hØü0üN 8÷4‡{¶é‹¿¬~ÿGÒ‰ÙªDÒ UÛm¦Œx©LÝîBפ¸î ÓœV¹£c%§ Hyâq¹Ãv!?.çe–»|¦lÀ²náÉ–=’Ø™eÉ’L9gLEë èÔi°MÁäøâ°J Š$W¶k%¨¿.xþ2u Äܲê‚Gß~èŸH,½4°ŸôŸÄË}³ïª¬uÙ8ðeúhú£Š'ZÏznHFtHT|Ýã£þ#î_ïD7ë;F‹í]Ç <Ùeñ¥%Tê5ì_¤Ô°TËQ8Çï©×'µë,¨M¡áqKm…÷¹§x*¢@  ¤–éCOœ+ü0®憽 >¶ÈÅkÂ3/“8nT8Áç~*ƒ¯ý óyö⩯¯Òù¾›¼QéóºÇš—)Ã#^HqõJº¬O‹#QÍ’HŠ×Ä€ð?d`U¯Xc»©Ró§@‰&ÈÁ:Ú~*¤7ûɤÎ)ÀÕõU@&OŽu®T‚x¨DL#J_waD„ %á9«’ù¯[7½$Ù;¢¨×o»‹– P_McÓ·)›sÆh­²!ÁéwÓ Ÿ±:µwÃóסø`ôDîYÏè×ÉÞYÓA|;îìT™KÐXËúÚ]»ašEs*Ή>ƒ>ž{¯ßFU¿Œ‰jØßµfm* 3jñÄž EÖ?Á¶¡¶Ñ ¼U+âÁ¸b&yÿ$yÖ2 ¾ì04¡Â{‰)”AÈ,ïåÖ‰‰*Ìà8jˆ°üæÑ“’ì}*äÍj ‡g³ngm´àNE±ÏÆÏý&öKÖ¾Ìäý«CûöÌi±èL½Öc»ºi)ŸŒ\ý7ÛáNÂ(<Џâµ)ô¦©&½Ü2èØ©#D}³ª&ÇÐ$ìž‹lOŠDÎDdV(¤Ë¯‡ƒïÃXZ+g´ÇÌ"ñRëhömÀ+ªŒ“´ü!O¡k¨Ìv†÷L7¿ )ï-Y¹Ù‚ŒŽEƒtÕ8{ôlŒš÷ݨ>û&(MQÎR—I•ÆmŸ“áG¤Nõ‘6CJe$²S{§Õµ5xÔÁ¾Á‚ÂÁõ W}$Se‡˜Ð®Ôý#’Ö O8*žùå'ª; ?:ìnù¦ãæt—¸(Or´,‰Ynû¹æÑ%7ÐVDz·¼Ãüóä%ÕGÅߦœ¤Â‚ùáðÓÛ, WùàqUðSÒÒr½öêgÛ!õwys7µÕEïQ>sXYö™vUñ­“Õ*:L­kÖž9bjÛ\ShOÕ¹Î}3áq–y×”­`e®æEiKâ©d¢uTHøbT¿!zSõ‹ØwíÊa†0ÿ-À³æRã[æE"mæ0¤¼ó¶!˜œ]^¢=ü‹~ð¨Ú ÎîbéHUÎFü¯ëÛ?»prfüït»¼µÌzöñMíöÑ’ÍS_„ôœ‚Á ýؘ3ŽùÓ¨ªŒö-“jW>Er?ÀÙ ‘Õ‡Yp@¢ÉÇ€xâCÖ*Ó¤eC§µ¥eÛB^A!êææ°âø)›¢JBùv¡,ViP+Ö´²ÿQ^oqb“7pho’ qÆ2HЧSâq{¬ÖfÞŠÄû>z˜¶aufûÆÉ_Æ-#TŠò_Ç C#a<ºïºz¯S¨áÓZ ƒ?š09€Ä „³‡÷# 9Œ¥9en˜Ê¯ª.“]¿7ML˜‚é½µ®Oe‘ÊÛ.l’‡åþªÕòm6âøÇcçÍsàËSußOù™•ÄDù啺È:¥{Œ8q·±PîºZ¨l×”ñ™èm ޹>T0UVªUýL0  ؈¾)®}NÏOcTv[ùëÊ øŽû¥túf쮹hæ(‹B^/ùk !*fžÛˆb„¬5<}—ÔŸfÓ} (Êvâ(t2ý.xmè‰Ïòëe_Á{&Ó¸åxþ/Ãê‘ÁÓ–»½r‚Cà˜² U\¡#8ç8™C¿‹­å²ÓŸ?ië6¤ỂÛüC}ÁŒüE’G¶ñ;D{ƒç…gʉœø®Ìl;šµÐTp±‚ïb¦]r^ŽwÖSü&-ÈÆmcï!Ž¢Ð5ð‡XBlq´Ð< à©0 ™=dчE ½2/âÔÝgötR)…QP@îÜLpJú[8  xï SóT‰þEÁ@`¡ºn侮°‡a¸g«ŽéLîÎhÕp®#¨¥_Ø_®}ÊšŒŽÒ[ɉªª;qŒ*jr/ÞŸ%wÉÓ¿Ù`ÚRk&*½Zv̓+òp¤éýø7›gäeâ¼ØçÍ"­/ó ‘›©±}ˆ¦w$ùÁeË)5–p|ö—?‘©ÉF ׎Eb!îœçô4WÓºÄÚíãØWë¸ÙSýlaüJSà–¿q^•b@Ó®Ú-²+O¼µ”Óz½WÏo '\=eÔUÈ0!ÉÌ( i®7g`"—ÐÅ[ægËf7óµéË©©ÔAíó’O¬Ü¯ùëŒ2uߪ8=…;­I¦á’ÃÍ©&Þ‘Lò²¯_Џnä*9&.V)"Ñ<ÍgeŸ"X­ì#ÙºØÚh¯5ø!Ä7¼W‘úÕÚàOà˜S±‹òÏÔÆ‚hÛ@¼+ÉÃííFohPû{8ä&fô  ( ¯µ}ו•ŸxÈK”1ËÅ9Ýs˜”Š#¸còWîÝÙ#o1´p4÷Fnª;yOÐü|EFSRpuùH`¦I¶©¼ÆD‚†`vÁ£ïâÖºjerV/C Ž0´R8Š’­uÆGKʵÝQðAûfŽ1 ÿÿMå–q]ìR…v^ÀBC͵É5AÃÿV×<ÐÚÑ0žpàgúE‘ÓjxÙʙޔ'Älê@ùH+A6·Î5aÂó‡_Ô+È#TÛJoÞ׆¯‹Xd¡³L}TrukW„ ^¿„%^å"Nó;ndlóÿcËntP´ôÁ,Û$£~¡ÑC!ž—‘èßžäXƒ­Ôþ…8›¤è(ºøáô½êLÜVmEF‹tø{é‡!¡Z·®˜HŽ0xšiSûÀêàe¶UM•qc±Ê’åé2“ᣇÅ|ì…³œ¸ýñâÑ0H¸®œœ¶LãJ¶BÊð–fâäÛ8ÎöFÒYu#Iɺ¢x ¢Ý〬û Fz°< bÆy}O| fH~íj¶CÅÙáìþéEdt˜›j0_zF¦F29Øt ØË´uµ“ježžU(P'V ˜Bu\(…ÜF¹ ¼,©’ŒÚ¼7ŽòUxÕ ãœ”#Õ@ÉÖá‡3Ë1­$:»0îÙ 6$Íõ0þ¬q«álÚ| Xa“^È%q ÙæýVßáÍrÄ )6}ÊÌL£¾´÷6Dô¦«- ç}ÔY&"L/‘Óš±JÎξ‡zX:Мcÿ~ÒôÃÄ ùtÈv” OÒâÂà|Xh¶à î¼¥·Û¼o±7¦„S®O" +ƒs{jqè'’1Ïjkò¥˜‹D~ Ss…ŽTN·‹‘ßR1N)FŠ®·GªÃÀÁISðIüô;ñ$7A©¸ÇA¿æ(ÿAo¦ “uÀŸC󧚃ZŠO°=•êѵ‘Êt¶Kñú·Å…€Ñî4Ÿ­¥ÆÐb¦‹ÈŽ6”$lœc€/Â*=Ý·‡ö+¢Þ£ƒˆð]¦yÔêê©f2\¯ÕøS'½±Øª8tÉòe¼½fÄŽ¢Ï¶Ì¤ø—KqUÝ)Kèàè=b#F(bƒ~HŒ·W…t<ùt/éFóSÝð{VT`ÿj [,:’ÖÄ* x ­Àj<íÌnc,æeHÝú­†F©1¥•¬ ɶ¡·Ð7Ñ{]HUúüH n þÊ"+3œ;¨mUi zÈìówÌäãB³ˆ<†ÄYåb˵Z/ Ñ[¬|²2¢Îl‚Ì"WÝ/}˽=>»êqÔl›!]¥LqãîsNP¬ç³vs»êigWÒçàjÖx.]dÉŸ“Ë™ [×ì[-æþÚU…™®ÔSA|h§èÓ$£-™zèê1´ÆPËÑ ¬|ÔèVN”µ|5S²›ÐñšMã¡°ENys´´V²x‰z×~÷5 â²bLÕ’õ=g5æLÁ4c.¤Þ‡è)¯¦ŠØ4©äå<Âl¾ú®è¾ú&sž¡wV˜ g˜¯YN²;Š’I~’ؤ ¼Þjã^ -‰Òšñë\^èÛR‡ ý‚2µ1”?Ò}uÛ_æËÄ.œj¬ªb_QKßÉWZËT·óN¯p?vÕ ©Ùƒä«Ñ,Û›Ó¯ws»‰çòÕL»(IÕ0Óp1Kå«WÜÝÐ?eÁÙ(Û®/å ¸E@GÕ9c›m¹t³¬|Î¥L§;ÔmÏ=0~4@Óu#‘ˆ°´úYÑ}h,3týý¹µœ_+eI›q ä®÷í¬’ 2À’T§PµÉÑp¶i`B#HGëƒ<…%ùí0YéÕb·iD9»¶Wx5C%°`Ý=œ8š[ Û¦ÌdPÙn³i ¾F®ˆ£î™ˆ#,ƒëK ð á· ©Qê;Ï5ìßê>«½”@z¼¸˜}ÍQÞÜÔ@Ls­`y…bð°êFu”ù*`ç‚™A<l®¼2y1gÝ4ü§(Öãl^vã³Ê·ÿ CÉ‘nšu(,_õŽ@ÅI9L¹×!˜džw ¸ò]Ç#û®DèÊz% ‘i ûƒ _5ø:?nbׂ€ wüå …ÏVy0çµ$w<Ä¢u‹»FéqߢŸ²‚ Cp“áôkFUó¶PªÄJ>’YAï<×™”W71ÙÄuò¾@®†2v £»æM4G‚­L¨úÕzå+Ýp|Òšßsµ2gjã×ë;¨ÅñØxi¸Îõõ#$VUÆŒånd. DÃÀ†°-y"—n·NÛªvÊ }ºvq9HD½„$å‚¿zË–K=lt 7Wé_áp—5«) üúACß©ùSù ]üÔ’dž“„fÔ¸é)ÌûG:©ÍàïÚ¦8E뤸"UéÓköaCR\CR‰àoúósÑ%ïÆ `$øyé›â‚²2á…ÊÒÄ­™{äÔ`AÏäM€‡5ɺ•¯I2ZŒŒî”'û¼ö 'ˆÑ¹3 ?ô½Ñ·åMž½2Ö(ò—° ­o*kÃíÜÈ}»{·mÀæ…CKèJéºÜQ¦™¾m?i6ƒCõ`¢|Mîqëàà[áò¸fÂ}t?ÜlËösSÈRõ>8O%˜ ÿJ¸r†qÚ“7Ñ[”GVÛ7‚0îjäåþO’ _sä5Zƒ÷ѼZ—œ+nC‘‘6¦ˆù{›ÅþKS’T[™ÉœG¹A’•Îî¥.ÌôèBCÓ^\¹ô=Ü) f¶+dûì X~¸¬Öê¸Ožõ¿Špe6Á/“1%Ðü/I DsŠo(Ó1·86 5(˜ÚØMš¥—è°ð’gˆOž,‚Õ`¦(R›Öç™·:²ƒ ?AýÁ;cþG£…ãЂiƒ~n hîøÁ‘.–< ;z°ùÐHiÖÜÛ~`Öç_ìïÛD°ÝËK˜„Ä ‰”ʹ“€r|nQ(2 4%¡“¸L/ó¿uB±ñaRtÙ%»öÎödÛuγÈÖg73æ~„$}È&™Ÿ(ÑêºpÒ7ª2}ÌM'©Ê91Óåí¯C´4 ~½úˆ°q¨fŒaÑÊŠÀf¹öÆl;møÙù>b®ÈØD&(ò[e%§0(EhE™›ªâ·e€NÏÁp)¥uÑÙzN¶9ƒ@\8¼þëùÿoÝ„”‘)…«>Ú|p{’”T…οöíˆAæV Ûê&÷âÒDÉ“rD¸èœ¥ä «~i¹ô„Ý 7š² ûh:<@øŒäs«™‘—TNQ.S´Š²ºcs¾]˜š»¨‡ÍÆ"˜Dü[;Ôz¢'‘_œé*镊FJ[]‹¹Œ«ö®hŠŽ&¥ªH2‹àèÎ-\HrДîœ9vû+—Ý7f=•„ÕóvÈtíqg…:Hà…jZ½9B?5 H2CóT¥}cΈs§,6:+W`tNFzÚ‰±uJ]ã@òoE—Ûõ41rŽBi†>1ðîÙÔ‹aŽö¤€\å§I“Ô¾ªá(. 9tµÎ!ýÏHÂêÇw² ^­—5*ÐþÒc~:IÛihœof]én2ý%+‚n‹u,!Ñ"‰ºJäÁ­Jë?y»Ch*Õ«çO«z@ÌüyìÈ›'0õy&s´úÑçUê¾ÜŸs,Å–P¡½ªD/õ㟉“ÿM<¹¾[uú¢ÜûÞ#Dõ³’¸æ!~ (ù76щ–”ƒ "ÔEés¤Ìñ‘ OͶc-~<þ—Mð>D©?ÊBöu¶ü<3"¹Ûsgà÷è &‹è…ÐO¥‡cz’.½×Qµwm•,Õ…hÿf”Xe:ÿNA¸,@¥}¶@@T#c7¬Ø‘ó °¯ÿ:°– æÂT_ÔkD'­ãrb±Âe¾:¤I¼Ý=PÇöY¬¥¬]¸¾¼Oò'¤j€²ÌÕC'› Ù ³¥S3ƒ†ÆÔUoY#}-¶Ï}-„þ"i¶ä¢Ööš 1êT©Ñ»È@t>¶$ä[Þ²2sÉ\£OÿtiLàŸ>#uk)h `)-Maú’Ìm ZSœV§°…Ù¾tIÂHÖèwÄ(Ê€%©³Â.Äì1” Çe©ºmUÚÏUU$oŸWâ Ë48O-e¥m£ÐnLá‡CÕÙ-¼/™uù£—6t ‹|„ƒYï°Ñß †B¿ìAGns:K1¦ñìs‚ªîüd2‹ºAù¤€ÆÜK”æð¦îŒ!7Yª K ¦'ųìEÑdaG=ÖÓƒ¨¢>ÇáÞ´~æÇáÝðâ…xêIi7„§!§’#èÁ}Ê‘«Ìåv)¾)z|0Q_iWPp+Žß¨µ,óúW¸zÓ½¡ÁÚìJÓYRû«Þskì çÜ'‡´MD8ÿ5µ$Õ¦/¬6 oâ$܃õsrÐå=…¤ávS™¾&ÖržÚ­%Óͯ“×7=5?‰[ñB­eAD#2à‹ÜWS(˜®œuÿû“ä ØÒJ£í­ua·æ  ž3®j¼ä±ÝˆÇ«ë:ÃÀQ7‰©c –§èxÊ–ø±ŒäË“ IË7Ðí]ÐI×UaÂùÞGå@ÅÌçÍ%ñ×{\?å¾€Ê1Û8Ƨ }moÍ.³Ô|ãÔe…LÛáŽÓÈ2fa×i·Q`·Dd£ÿnŸ‡¬–‹Û†:^%BðÆù./Ñ ´4O5V !sgj„`StoCmMÈê¬ ´û=|îêÙÐù÷•öý¥õyÏ­¢?„{Qéh€LØàÓötÉ’Ÿ‰4d–)h.îÍÀ’?·/¼“â2ç݇¤uø–·‚”{¿o9õl¯\jÜ\ ±Aâ„”å/•1"ÿÄ|£AÃuf=«ª}ì”!ÁRˆ=é¹SjÚK-.Åš\Ÿ×²>p÷Ä!Î*ˆÀJ)\ˆ €ù]ÊžšìÐå0_ë3¦û½¬òÚb1©Z S >¸±Ãù"Þ^Ë*C³8×ÂÅ{ZræŒEóþÞÝsÕ'"·6±xÞÌo"{‡¯‹0¤¥;·iHq"U|¨¥fùCÒ’aÁþ >DgÀ›Xq1Wy­axÑ«pJ„8Ê,„ãQZW¿ýGuø ÖËdÐ&¶#‡/d¥¥z3f=ë1µ[ð0¦˜ìTÿE–^+™ŒŒu²_¥¡Aä1ÛμgEƒàë|œ¡þ¦îgˆ–mtûý" ¿Æß19Z4ç_rµ@î©pAµœ§©7šê7ËÊÀÈ‹ID‰Ræ†í<3€žËàWÞ×/Ö¢çÚåœË›ïxà¾ÉmÔ0ª«¼(Ï- 1e 1ü'à_TI>£·&±ÛÊ5æBzZuT˜ò--éüa™3o Å–Æ-VXâõY‘"Alh¼ù &8†'Ö`¢IXácÃ?­†>æàw ¼j$˜ßN½›6_rì$Óú ÕÏÅ#^I°\µLk‹3ÐËÔü.w³ýd)o,ýÚ˜ æå›*ü ”æ‡ðî®+TpÑ3剡µ<ûªôXzŸâÄûŠ|ŒØ:»vꊔ«-€àÆpS üDYu4”à=0ˆo®V‡tÊ—ôÙLaPvºè0›"Ê3ÑzÇ%;ŸŸa]Ž©´€nÏ÷ÛcþŸü(6gÞõ/n²rñšô2޶$ª¤Œ…ªÀhä¬ß#[ål7ÅIZÁhP½à{„öOÆ–UYžæý«@a#¹Ãƒ{ø­ÌPÇЄª!³çGÇ “tÒ±jL\Bü”Œ!Q[ÚGàP³ÜP6Bã¹§*~ÎÄ1 ðœÅ€øî4¼é9`JEÍÒÚk)1-À›Žš"?2Ôݰ¦¹°óGSÍ&¿¨ƒyQ¼:(€×/‘ñ=À¿Ç2Ò…n¿7]ÿs”²¯‡Ì4AÈÙ)œ_þ(\Ò*5þqNH2íI(`ª]»•êßÙÑäÿXˆÚ1û@¢Bš‹ì)ÔjtŽ<”òU ;æÑãøD~iá4%ÉÙ­µÐäPMîŒlîÔ…üDÜdÌx˜å¼P¨ÚœÖ­ÑæM¦k¸5ÇYK—ƒÅ2«vú"úR™ÈH’¨'+Áu©&¨·êõf¥~)¹…‹›‘,ÁžÈéÂËQ‡p!ÊA6\ìéœ01¿r’²Î˦ä¸]úÓˆvCðø$¸|}oxKx}-m„fŒëNo4ÞÙ ´”åãÌD¾º×ÖA‚*qÑŽ~ÍŠ)z¶Z¾ äd@‡_2bãÕk mN²l[Ñ3©P3$†–ž½_×§­%a²’˜¿6/ý¢4)6?ÿ c'HCG ¦XÍm.¥W½<[¶,K" l›3öÈêk>ÛE"k†¿6ñ ™^ÄLmŸMQÖA8z*y.îÇË·ëñD¸.cÁKÐQ1aª#„y_Vóª\16(ï³@AtÀ‰-†ƒ„·/úÈ#ظSÿUTfÇŠ}9ÈÆJ$ZšM£ÙpU\ñ)d¢ŸåÕ¡ª&Ížr²˜‰­Ö|V™‡se­ßþÞ¶£í^ZðNIB6J3H‰r%ã®X¦s»WÄAëRå+À02›P¾OG†‰ºuÚDl™õäoĦü´ëðë§ùõüû¿±rû{þ|ßoa7Û×6·«OÛÔçíêËöôþñÎz’ÖdÂq®y(o›çÂ=‰•ÏݵÆEï|± Ê©ÈÝÖÔñy•MÃпJ¨]½mc{ÃÓSezûAx2#™Í—˜øÓæ÷¬äIV,ø0™F‹>«ê¹ L½ —úâ’¦ŸÎ.¢\•e°|þº5:ŽuqâþA‘5=(ê…G=‚˜£xd•iG ' 3à¡`Ö¨Aé[0ÚμT@Ô;4Òûù|%†$´ÄW Ž¿è.x*ý'è¿Ë§ Îë¯õy8ü·ª„Iù«:ÛÙäaæ.+8~Ýç€Ý3ëIdÉ`²Iµ•Ž/HÿyÒêt¬]w*À®îÔºòC^¤*oùè¥kâÞxºNi_ €öý'œÞâÉiE_û©XYá/>5&K¯»LcúµäâF»ƒ¿œïÿ ˜‚%ódâŒt¥pÙ#á]¸éÁ¤Å[Pw 7¶ê“kMñû´qZ©F™ ç¤ÞlÙ¼(Kq r—vÓ‘R[þùb2~Ç9É@,P?õÏ ¼ƒÉ¹í†vbP¡º1¦ÛWtœ‚S¦,©¹MïxCørƒ]I‚n(Ûõ„g|ñ¦Ì1 À—ÐXÙ»ÛÐÝï@ÞtNP7i…á¹?Å{;þßb]ÿY±]HþMJýü»™ÿrú;YÝf›”)PäÎ4¡GË ièúæÇáRQš»ƃ}ŽrŸ+u*B‘]A˜àjÊæbâ|… jÕj¹)µUßùÆdF­8[q9Š*¹«/»yê^Ž#ê˜ñG÷¢•Üô"QskÄTŒˆ<ÌÊÖÈÈ:ö¨ºÒYNQ¿ËŽL=Ñ7Ä¢} ÅÉÝkëµ”.³?®³%äS„28À®ÀA½ÝÓwõ¨Ø#ˆ,ZW!_+y»&KM“L©¥2Morœ<úÊÀšZn ¶¿’9Ìz#–ö!¡:!‚!ÆÈ‚´·5ÁѪ]xýe&Àë6¡èÐ ªøN¤àZŒã•."Ñ|g¯"k§·¡ ÃÁ”,HØ“9©·Ld: ”nL û€6g™•‡ª-B(|±–õ'®!–%H(jýàI%£«¶ÍÍÙ®ùÆ ~‰UmtžHJÙ´×$¶KÆÑ€#©g¼Dõš$Ø‚Ù'$øÚ=ýæ¾Y‚¢‰BïE!?‘Î5ðÅG‘½}އöâÂͨÖ?Ê0Npî-º€ÀЬÿx±Ü' ÑÝ:!úýaxy¶q¸°k¥ †9߀²KØ”t„š`Uʾ‡f&þƒÿPN(FïëÒK¾.¨GŠnú€8 8P–«:†RlRú·| KxàèжF'àWSWx cçKh;ôåªpJ +ï-•*S»¡ÝŠ–®¸6EB'»ÖÂ;)Zé;†J} 9›]~ðJQÇ×OryG*«?ºßÍB蓼v§[…³üØAÄ â8CоZ¹ær†Ã(‹Ê#V€ Sk‰(cª½kÐÿ)þæ!uôúnŒŠ§ûRBK€%¹óhmÚ͇„8*eå_&’m âéæ1#¾tM<9 7ì#û˜Ó0u€QNõ¤è„\¡vús'aEûS•_4W=ô“jíëmŽÇÙ"H¥€¤‘o½"×úê÷Ù2öb±&¿ìî¢\‘¾ØÎæ† ›É’qX¥Œ³¬|¬ŒËuü+ïĦH=|bÀ¸D²Hl#*8²«ûT±[ûGÓûÍ”u"„œ[KÃè& ~žòåÔv]ŽN”Ó†dg³D ¶‹zÆ*r8p\0l4c]îãthYI=gÿ>NÌZ¨ì‰ó}/ÌÂk‘ß´¼îB€“<×13z,£‘Bðz:ò8;·Õ[šÛ5iÕ=] ÷¾#†ž±µ6=ך >ÜdNnl†Ë“˜%ýà©g ¤ŒQ…b~ÛŒÊBAe¬ZŸ]€†-Œ´DÍ‚ZÝü7Ò¬«ô\j®¥!y'×6íd8ú¬¾HË¿úo[ÀHniµP ÞáàÊd”¾Èß—ñ¿å@ÜWð%ˆølÊn©f¢ú‡ïê€ÃÂq}±Eµ®4õØŒ6ªÀKR@¼u¸öîÜâhû蜜ø?p5KÞ ¦{%úð9Åúg2ŠÈ’´ÕS“`¦'½™K—mß%*s3{ègþ÷^¡9;Ôº°U&+˜jþN…ÈUÕYóq ¾ÿ=Ûë¤uL=³ù“–ýì‚`?ôžI‘ÐUeùÒ<ôNèjŒ£C5_·víº¦ ŸÐ©Zè7Å^ámÒ_‚»"{J4†ó {Ý>[‰7>œºÕÚe°>L³o²“Gþ˜1ÄÀ2MA}®è ñ¥jT_l–-½NßhË …“Y^–˽0&=Û™ø9i~lóò†ys"£¶Hó¨¸zØß¯5sXÚe›• ÅÁ‘M„IåmÊ (ÖÿSHru×±þ?9e¥i_ô) <Å·†O¾ƒõC¤ÿa$nÈB\Éÿ8ÍŽ„†w[¾‹ þéǰ/¿/ cî]¤ñO¶0‡»yß™®y; v ‡jàbu Ò°Åê6 ¥Ã}«œ”*l—•Ïro\[}3ò¸Ù˃}È3Ü)°X/ú›Æ Ó[ã6VmðâBÁagL`¥¤¨öóhŒ¯V1ñ˜M”óGÿ]c9KÞS&„2g~¤ÄîQÁ¿‹:ëÓ%äudlå1;á1½ò±ÁÏ¢O íHÑ,€Ø˜ˆEê¡pBVK´¨þ)Ä É\ö™€€Ã/õcLj²æ“.Ò‹4†ey\…;RB”Ôàz–J³xnV= …£õK°¾x˜@¹XjºÏ€Ê4;R„ FÈ/`ϵŧ'rÆ›ŽJLäŸ)ªs„¡7ª˜Ï:di{ÉéwôyM<$E…3“^ÿl(„ΛÍÇÕ”ؤÀö-¾Í/ïküåÀ[r•2p0»~ì¯H#¶ËãÁ uuQÜÓOÎ’æØMx?nz¸\ÁBꯋ“=ëØ }¼€&ƒî{·“‰(GPÌÌË*ŸJí±SröõXÐ f†Ã^¢•`%½Ÿ'©E»{'ÂÇsi°æªFä·õBiøZ˜6í>œh1`++¦¬øŽ QmMveö(Q9reŒúœîü!S¾òŸ:Ô1•¤ñælg\EtÐT,¾Ü+°<ÀN%±))@~WVf‡41ß¡³G[ÕeiIèg¸õç!èóàÃØXÍ+vúÓ_N2í|_6ë¤ñúg@‰_òË­Pûx”€Û‚®WçÔªØ[Ô¯ú+\çñ}ðËÖ´Ó(£¿!++ÛÂñeK­÷R0ŽŒ%éï‘…wð¸YRX–’áДÀ ÿ1¥Ûˆ>£x|Z¢" E[cÁËöTZ1©QÿLÆùè*iOÞÊ Òô[{õ#F̺N|5¶çyžŒ§î̾k÷dz­Ç;Ó/ .uªáÍXYÜ¡ú‚L¦+sÙŽŒ½"ôþiúF§ÌvG§ˆªÜSÖƒ’SºŠEvc‰/Só³€ñLKv§\PöËöýϬåÏksA-[;p®B ýJž»¹îžïoÍéöY §"êX0²¸jLå P–BžMöÀ´ÃWäû Ëš8·ë÷—TšãRú:[gMbI17š?#õ7÷zà-è j€ !4Y¼^ Ê|ŽB’^å`¢Õµ‰ BœÌoíú½aÿxw%r¶2ði-²Ïú‡~¤¬,¸=Š<ÍO1ÃàëZoZDáYŠY©úÏf)Ä/ÀG-Ç¥¸-Ú ш׆âÓ÷ˆAGÉB²Î7¶scÀ†º<¯pÒ<ä¹hC³óJÜm$Jß×2¤<}riÝ›g/Ýã㕇hê}Ën[8îh0NTfw¾p ¿Bz.­ÁQ{©›w]šf›|¬Åç埢Árh>/祔9ù®yß/lQ=ôSÐÓïÕEr/-üºq»³EÕwìÞ=ùÇ^†.«è©Ò0ËÆî4œ:Èœzb,ª>𯑸ðÍ^êÌ 9nüsÆrk ¬CËÂùÅ®Òî¯,x_‚‰.í~(=¥ð‡xO?M¤¦žœ7ïºe\›½”ëñü¨‚YiîæKÜ[ûÝèòÔbÍ3§ñýVþž\™¿ÏÆÏ²þ„/e§* OpûfW^=Ö=Aºê5§,Qe\®Id‚ì°;¿£‰º‚vНq•U›{¼`’ Í_ƒÆL…é¹fAu÷g ´Cªk½íc8E][¯¯zã‘dY ¼×áïÀ‘+ÕÂÿ:eÓ*$óõ·,&‡vª%=W¦#ó•v.D6üLJPÄóÒ¤laB˜›#¢-ò‚j,Q"ÉÖì‡ÛÊ XäÁÄå>' 9M%7üÁ]2"ÔŸEPÊøÛ±¯âÑ#âÁCC2æm“:Ñô¡bÙüˆ×ËC·š‰m‰4¾,tX5Ìo’ñ„½#ø‡ Ó„‡›ÀŠ`_(Ë1EÙRâ-­?}¿Ú™ßê¥.8áÅ­¢>SÍÕëï¡!v 4êé­4†_š]ÿ~ò~ÃZwÂ3˜cÓóóó-ÒetcÀµF–öüT2¹µg±V #RYø”$è½K.„Y2ñ“'¸ãT纭¾\32D³J–Ú–=À1™c|‰ÕËú:–m364)ν<ìŸ_F û›Òž»HjÈä5›o Ád¬ŸëÊ{Æ"ÆÎKòËEÆÖÙdïj”diïb[uÕ^› q1oA‡áÑù+îÇ ”A;;aô¤ˆ«È6Vk\Tö)Tcèë G9<Ε“]©äÐö‹Ä¥­¢S‡ ƒ{![¹ŠdÎ,¤6?0†kÖ™ë(÷÷L dõŠŸ‡¤ ¢Þ{‡Nå±HÐ&ÙÁ­ÁÈ׺Ô0êÔhZa¸"é½²Ötõ¡©•Û¬eVU À\_zß½ƒóªÀK=èJðRËÙvžŸT[åîT1æ䎰ô{ÃÑð&ùúë7UmQ""‘?gÍEqÌ,ËK·îÙV\ëb¾3k–¦¤-¦Kû¤æâr[æâRÍ2LJ¯ ‘ç‡$H¦æ€H€–ýô 1¾ôçâDÿ#­RË›1Sß*Ñ6}°Bø—Þ+ZŸ}˜Üæ1áê‚ÊÒÜÈ…8q m€ðšË—¿ß;Wó@ÚßfæûK?kòX ß¡Òâa ¡)“Áˆ·û€ëÔ8$Q|ªz“å}.Îᆗ˜#hBü÷œîE€2"Å¢5£-#ƒ”ÒÚÓ˜_uý z"ž‚á6ÏOH¨í[»P'#&o©Ã R¾(i#8ÔÞ±J³ÛÝŠ¯åü? Úúï8‹ „xÚ9èfêaPA·ä°Jƒº -[“w#uêÆ½ƒé°ºn*ç#á…ÚWÁU$Ê9¦ó´P¿¸àîÕ»X¶€[R¾ äËGoaà¤:Ë6À9»æ ǵ"Ü®ª$BªY/Øœ5ðOa#O#ÈUÑe ¡ŒÖÝhxW†# u9ÚPu™˜ø”½õ6äse·ü—ýîOŠó®¼é–j1rhêbYÝ»lئ{uLÙ¼˜½jž;Ó¿RGu³]qÏ×ídY ÞdûÑÝË^ + Kòý;Á= %õWø|»º%ÞÝ–èD~!_r.ž³ D¥Ú‰‹URve!g=Ô‰·P O£&>úX%OY°ëC®Ï¿]SøÕ q€HÈŠƒà%7‘lâû]¨#Ìð:JÄÅw‹Û¤,QB4—éªs8x±ýMy!cN‡ç÷7µF ©bG”\BF{Wø».3ùl°Ð’¶ü5äˇ?Ua۵糛÷¥û.£ÑûË ÈÍvŽtæÓ0’¼Bß+t­ßÝ…+^ö£[€¤µëÕ½ýÄÐät÷yZñÙ3Ú[ýg EÙ1á „Kb®jË» B¶„#ãÙPcÝMÃÿJQ<á¼^‰œÁV÷!šà‡fË_ÂW§N&Š4±y Áãˆ,è¶G÷>9n`ö:,· ðâ§%å@\ 8p“ù¦+ e³0 Òîÿ žG-‡‡-ŽcØ !ð=ýäoè}HÞÀcÓM¿­…qû è~i¼ëŽƒÊCYäª($ró„Ù< !ƒB½!?Ýr€*Ýu?Ô©‰ý†ÛÍ´ù’ª–µD<åMª_åFöÎ ¢ûç]ßæ=†=c\7Û«`ÃvÆp‹ùþeLÒÈ rc‡ÊØ9JJ|Üö±›'ÜùñjmA±?ÖJ¨ýñ\ïÇa(]€U) ¾] ån ùî0¿3ñEãÏo·¡ƒpë~$Ø‘š÷ ±HZb;”º’ÆC°„(K¡ž¸§·*Ž¥bÁ¤!¹ˆ¡¤ãâJ‰™,§m ’¢›ùÜ´vGY¢p™UšœqÙ¾µ.õTèqyÑ áððÅe™ ùöŠ ºËHð¦$;*ôX<=¸sA!éåË\1 Ÿ~^Ö¢ŠË(+Á]êá‚çfÞ#<“¬ %^Wð°³Ñ w@;†úàp9ä%e¸&ÍÆX‚„3y¿8îÃà„˜]cÏR@„ýÁÑ>|\¸[û”¿ú¬÷eÍíäÒUÜí9_ æÙË ãí4Ýnw÷ xd§±@óß_h¡¯ðc¦—#åp—Ò+&ÑôÌ­d‘9—¨úO”‘wÍœtNM¢ í‘ïÕ ®ê.·”%BwAFÒ!3XDW ôOG$ÿLêX‰ÍÈ XœãuNŸ4œïL]‘(‡È-'.8½-ÆÓK Е¤sNOü‚F³tæàCºpšyt¤HÿDMdúÖTò î£Öð¬¬zR»ô>qžGÌÅø¿ü†šŠ.ØäКþ=VçqwNP?;P(áT_ç‰Ã~5r±‚ÊšoªÎ·µ1q¾tÓ$"êõæL®â aZEZíÓKø*7¹ÄuÎ)Õa„@ë7/i Š!XfÊF’––½Y‰Þýƒ7¬BŸX//3µ6cV%¹.©_ŽwbA%Ö÷¶:d=¥jå¹yBâ'Å?!q?ý=3¶;é4(ñ¨\x Œºóaðúd.ѯ=³ûÙø0PEWÐ@Ï›U®ìk:*uѦþz&?‚×'Œ*.P/Šý°˜; bN@ Ý 6è»}˜Df~q}àifÍVohÍx —!èf øÇúÉ­`.L3K óÄR‰ïˆ³Cj¬GÏfûºÐ‡™*ã ýy•“?COy"'¯O‹,²žxtÑ)4õ8BZ¯§FÇ/G 1¤Æøþ †Þú씦QÊՌսÔ|.ãÝ!‹ol­» sz›(²Dz~±Ug°Ë*"å“Cá•Ú’å(bV†¢ÁPáðPâ… MÉÉ™tÐÍ?Ë XÐx€Eöí=ð]’8ä[pÓ™OP>±23¤q´U0‹ÇÓÏÄnöÏÙ¤l…\4vöŽ!F«g¦«Ê3:b¯N.d¦DŒ°Zìû¸4Ä(êIÿkosÆ´,† Ʊ´_4ã`YÓŒ¯KÚÓô)Êò1Ù²,>Í”ÍiS“¸µÇÆŠ2­óæºLQ*îS™Jbêê nÜ€~X ~»¨ŠvQA¦~¬DŒ°Gp®²Ùű®Í:š£BhxëµEhAç¾ÒwRY ‡VofíñIÚ9:,Ý‘T€Ü‹3™ÓÈgK°~‘s[à¾Håcõ‰*ëÖk8°ïÿ0pôkgV4Ææ¯þ=„Jƒl¸zÅðµ_ÐWj_‚ÞÔÓTð‹&–}TI²}37ª3ø´—9»ŒÎMù¬Ý°F!­Àäüa'LÚý¾Ý¾„é-;ìÇ©•_¿GŽÛ¨ï#¾%¿}ÿuw1ŠõlIšª±âwzå.p|v³…ÅßåÀ&Bf?½…â;j,Ë›”È«ƒT*‰z$ˆ+ ,ÈÀzõCÌ¿ÉXΟigi§‡^ý·…¢ ‘èÒÈ0ãæW4›N=ÅŒ머¨›ò£V”H¬‰(çÚÓpUhé êûþWð>ˆÚóqÖnðæÃ;4î ˆQKŽnD€¬3D“?(š¿‰¹Ì2ׯ¬v“Þ¶JTl£1ï YFN†>B•¾Ö#gëI®è³ê_$Q ˆ‹",‘f¶8¬UL8âðaÙ«›ce“©~ä;¿ySU°¿¯ý.‘¤¿ÌOj„Qr(çõ‰j ó€pgRK‘¤ÖéâÕt—…éÕ¾RïÏ<¯_ÜŸiRª@·kr½mÊb²(é””ÙœãfDO´bÓdùÒú‚¡Ê§í›ö¨ynZÏ–b@VnÏìä QZ –üÃ/Ž×QQ&ßM‚éI?86°b,ô*ø ¿ ¦ne&û“è5½¥þ_Š¿–¬Ú°—0ë-úûiöºèmÖ˼!ã#Ó„¡ÆJÿ~[ì¥#vÌO~æTu!M—\«êì4âÓYºB†dX~ˆ’±ËŒu^¿ÝG8Ý*›þ]¦ˆ;—@¦¥äýïc¶­Ë<ÊÅ*Qð5#BEb›»³7njYxrƬFBª¤Yô…\â”…d5öùØS g×$ÀݱMßHi@A M~÷•yÿdü5Ö5øÉ&^èå9!§‹éM®2 MJvÐCÎVCƒ]ÎÍòGV”ö t’"ñ3BŠ3&ÂèZ@´1Õ~æh£ƒ>«%ÕÅQÑHóÀÅ{¡æe/Kò'v÷\æÊ£"C+µao¾ï›ó³š›Ç6–m9§üЀNÒÔi4fóÂVƒMäm²p9(ërÑXÙ§ °p1*R÷OUª-p;¾CÁ챌åCˆî0pmTÞëBü)§à«KšK¶Ž3²¹ä4æ…8bvdÖîÛ~ /Úè{i€«„œÂ˜cþª'y{Ûg[¹t/”Ñq{†.®Û_'þi¤ÍÓÊÿoºtÈG—kw+D6õU³2-M½s5z>1uW9ÓÆw¶yî[%t=Ý åª0—ò¥ÎáoàõHHN ŒTÛ„N;¼½BoÂÇѹӀ¦¤1¶¦úƒZ2K_æV ¤@c®vOõ ö 8‹*lñ¤FŽb;)³¯]ð7m¤ 'Ñ_ìÅæ‡f¹²h èñ¡àNÕk%tAGéõôS ²=KH3‚dGÒ¦ÌÁ 0}‡ãmø`QƒÛÂÓ‘ÎÁœ‡Éx7?Çø oà;®@ÛEW8FZT½p@ÁAÚ²2%Ly!ڬǹkds‰ ®aÇUÿ_’¯œ«{mšLJÉ)`~VJSŠYú&6ÙË#KÞVhÀð¥¿îʹªMbßZ@•ì95Ö"ø}”³Lcæ±SÛ ä×Á 8è\¦5ÓÏêPI„ßXãöì_íØÛ·WÕ®8~­ ý[Sú¶ïõlF·a/«bo«h/«XÀñÕ•¥¥MïÜNè'd‘×/XÒ;¨<ÔÊœ”Á‰ÏñezbÜh)„5°m}U+§¸,2˜€=xpÚÃ@BÙfâ×VU•£c¾„ññ©q‚ýKãOKðµŠÒÅ ÐÉ ~;bèrà¼]º”^Œö´ºƒÄþéµK©A*a¨vÅ©¾ô‚û7w6Â_¹h­è˜ù-’ó9A»› €«˜•põ70 6Ëï³^pgÚ$ Öãc®]‚‡òË!JZÓ×EjÑd’s‘…S"˜ ì,!Ï>^ ®uKÆÙ¨P¡6ꃜFäÆÈœRY„yOˆBÝ-Ú¥¯Çlôƒ¢’Ì`è[«¦7T;×W C²ÏE@Bʆ#ÌAÄœ?4—'0Iдÿ<ätijð0¸‹;yJÑPõ|©ämm؆²Ç{ªÚBt’UðbR‘]zçUxÝh<Ε\cÎ C/é¾³u[™.©~ŠÏ†}„‚,oÿdTvblH»ØøVwçªîM¢]ŠÅ°G… [M¢ªT“9³'Π·—W‡u ÷5yæµ „a¹jí}hJA·[¶£}®Õ;ÉS.Œ›¬ ö ÚE:B¡W4|T€-—çq¥_ûüÎ&€ˆMšôlhF„z¨ÔA}v½Zyê}WEð™Áò¦é¤>·œdEJ\¯¿jÑ·ÉP`)ášPâšjÝʲå¹ûåD™]yv‘d)yt`S¡hDo·âM£?&ˆ¼ðä|RHW¡³/7³!ýðZë‚5Cžú¬LY]Qç¸ÿ.nJ;¶•q±’ûTËwzktP¹ôzží¥N«1lêíÉ‹˜»jt;eŽo\¶Bz‰´Ayø“Çíaã3º´á"AŸ¦¶°12çån2L80s™™¤] kX]Ƕ³h²/ÑÌëjcè8tDÓuòÀ—’@úØxUý5ÌÀ§¹¸­³õ¥ó@#çCÐk .ôSµRLR×õ0«³Jɵ¥©gÑ7µ’FåaÂ_’^‘)¸§i.Nô¡ÁÞE5ÐjÞ#®0_ô»&zòÐËLXlÉ]±Èn¥ÙF¶·ÎmÏ¢ $öˆº4ü"Z±—s§´-^‰é›±:W)ð‰Ë§§Æ›W²0}•$·¬:å;¥þÑœ–jcã Ë3'ÆZãÈ…0oåp7üÓ–ÌSà¼,·Ž¿3 Ô½½df["‘¦·Í~#œ¿onݸÔÕê­6Ç$%5AtÈ“p JPô@žA ’Iª#¹zªƒé–ZŒ?<>#uȰ6ëÚ½É[\Ìk´'aí§²'cUEÆ/[+Iyf 7гÜÝ:/'£b ÂÜ8†0ôyâO 7C¿zÄ1…G²k®H‡¦æg± ¹%úø„F™yûô@úŒ1¡‹çwSû©ôyÛ¦Ò{±¯ÍØú|G™VѯäÊ/2}Ù®*}°¯•Øš‘!åPÖ¸Bÿ]f…R_%Î{ºPC#¶5ákѤ˜îõ@÷ƒiÑÞ–n"DÝ xdÚIg²«Ï´Ú©°ª‰‡—íså²´§›@ö /áò‘lÍ}ៈr ö‚ü'Ég/oÜNÃ56Úó\• ÿ/Ûð½Z)¶–èú K^‰ô\ß žû•†^×âRYCs¶Ø"µŠïøù<£]ñ°Ñª&P}#Ò}ª>wº< GêSŒª oŸ<*ÂÓ˜ôIsI4ðpç /RŒÑâ¾ *š¨¤›—¹ÂÃæ&¶]Ìw+hL|ÑŽ÷~øj=AŸ-²¹˜¹g.ß~¡!rÒû¤2@´Ãx”k¬ï;ÈÏÇ81XõÚŒ¤Åµ_–?vR^ÇêGÁ±ýH,þ–LçÍêÎgXç ƒô‡zó¸N!t†fáÒ°’p›ˆ®°†tÌݵü.e¥„Ù§ØÆaõì¬Ws&çP´¼?ÅÒ.ÚñÛ FìôÓ Tk» Ã(3åïԌqÁHh BÆ äTOÄÈúHåÍí>å§³y•Ѱ˜‹©(/ý«U­„‰‚Áù†JÌ!!¡â@ÃÌ%Yu¨õ¨Êà³Ni`I-‡¹ý=ÎfÑìP#•J¹ ýg 1:/gAàp¶|ÊÕ[Ržo·ÿpö'ß­‘ 7ˆ¤èê²^0HÇס»+Éd‚ŽóñsŠË?úÍÈ’ÈÚ‡o áÉLÆb±¸Çß„Íf§3×…nÚsd ä$û…ïª_>/m¸Ô¬0#ðš´²,gSØÜ¦ì`=V‰ÁÛ.Éw“$ÿÎ$ 0tbÐyu7 ÕljÛÑC&âØ„Gí£ÉݱØ~9ÖŒàá"øÈëŠnHðÐAO‡ ÈQ¯s&ê[(pɾÜÔL‰!°ö–Îæþ ~àet"M<7‘È ðÍŒ‘ZöØ7Ûf­šlìÍê§þCŠšfÌ•ŸYâd‹Jм!e\Ì8#Sø4`WôÖ¬ÏHE_ž‡W 5] :™`|]¶\j}v©ÑŒª—ÐnªLvD¯ kZ5=!ýߪܶMÞzì1PžV1K\…'3yŠ«Ôß~¸ðülÇ^º…µØ˜È8³d·]Ý,³ÏP™á°!½C«°¦Z]KHGf‹3ÖFÕ_' ÇjŒõ£%šK²L‹‰ádê0•4}JZ·“ƒH!1 s£(§‘ÚEw꥖nݳ¼žV0®eÊ 9mNÝGxG¥Ú¶ÃÕøq’ÉÆñw|vDfxaéZ×Jh£ >ð6& *-„YšQЍaàÀ`rq ïwË­0ª ÜêÑšiDÈ«*W/qiØtb‘šPÑEB¿nÞ"Í­GÓ¼ÉËH¨Ö—;o(/IîĆfÃl(ž[ë·ª¸5ZªŸåA¹cWA…rÚV Ÿ&$}[q÷PN1Á>“Xæœ5±Ÿ^ÅÇÎY C’œ™õ¦²wÙQmû„gÉsHð‰OzÃ~ÜBÉ•²:À ÷!ðš-æN\ÎF¡åMó¿_[ç‘ GŸ±&˶ÊÂËÏ;ж« àn§?¯îÝ`XÎС~Ο%TiÊ%³’ƒÏSµy2kˆÚÙ@‡Œ†—½c©2‰/÷ÖöÂÄñ—õøßX@î0—¶…§#b9~¯a d?Xÿ—ÒDl"‚€;ÚÊÕ«,h=´‚\°ym5_­C–e— »«€æQZ–VÑ’a(‘(ÚD1W¹kÞ!ì€áVèãkMÌI˜ÆâÓËFï̉®€}n¬fIp /U3Ü\+/9*RÔñMê]öàÃfj“\ýµ^Xí5Sf8Ó\üøn µmÒ¢˜4°ôR>r™_Œ2Þ{:ó’×áQª 5§$7´’<@®6o7ÓË™±Ì;°†¦å%®_‰ªGÖ.RýU´ IÿT‘åžÌ^ú!\1ŽÓP`'óÆzÖ šÜ(a8r{ôæpz¥m￉÷,…¾évþ ~³I± ¶I:¤PûÍ(ÛrÔ“¯‘¶€ƒ˜L\†šÌ‹#Æ`\R.ÿ:Q^hja³éKAŽh™ç^ú·Àš¹¶R^úóeåR¹vræp‘Ð}¥ôñ²S¼¬ úx± 6Ô.‰-8\Rá;ÙM¯ªÍœÛk˜€ ½«¹<3¶÷eÊ î<ºìW©Cˆ®x]x÷Ï}∜¶ÍsÂp¢ã|h@ 6†•¶Y@;§zÍ)ö~ÔSzSt×–¶×)ô!ûÒÈ—9·>²ðÞw#q–Q5@‹xûû‹fâ 1âé¦fwmÈÜû}Ö=0ú¦ý,Ô=a ^öÙT½0„õÖò ·yU}â~õ ×ãö©?T”œW¶ Æ›X®ë†åcNORåÕKM"]`ÿm.|¸O+@®§¡$g#5 ì釽?–2™g\:ä¯ÊÿHˆõ˽ƒþ…­1‹ Æi;†}®:nÌ þ5 ^áAÃܸ7gDZâÔˆˆÀ“ `Wªfvƒâ²‰b®ÄTÒ¤àÓ»ååƒ*÷w¢u8Pcîð6hÌ$o`%ª…šv ®.Å’Û˯øRÃÇ]ÇS\(,ÈìnÕOüc_í*| ¯WN<œt54´Ÿ»hªƒœºXÂLSkp¯sá}7;ç¾ùÐ$ïķΜnï4¨aG8ı8Xç“WÕË &æu€'qút«³ñ@¾­ö'ôÊÌ´Q<0`®ÁyC‚ LÝcã“vÖtˆŒ7分Ԯ9%¾­½+d²ƒe›Þ{Œü"ìUAäôcKÕ6&‰v‡s.žñÎDZM=$ÍØyjå_áÒFÒNÔÇŠëø…7 ×8Á©Ûâ!.üªÍ‡˜Ì—?:ðS9Åà1dP¦7çÇÞTAÇŽ‘©¼­x×yy5>˜fM1šÍOd€²+Q_™mQoD¤qó f[‚†XZ¾ÄwðŒ’8•BÓA(iÞÔùްEAÔzý•hÐ^„ʃYkü¥ñåôÖ¿0Lì`ñ÷2ã÷!ÞÀÅ•Dö¢túéJCeÇ)ËÎÄðÐ?•u‰wZ˜Åc^A0#fÜB«Äª¨Žƒd£P–*ïtPs°Ó&²P'Â;Â¥!‡kÿYØ^GJ˜¡; Ì"0ã+è.¾L_ hÿ"Ti7°©áÜÕs#bÒÚ9ê\Ç®YRdíâS|­qW^ %£ füêLIôÿ%œÂ,‘\ëjXƒBü\*£Kx…U½±eT^¼9ñ”W’… ¨â«ƒZsþÇãÇ798¢$=p±ñè@ÆlaWìýlÊ_– þ#@öOÊêk##{¿jDÓÿa)xš]’°'è_Í-Î"³Ø7õŸƒCy÷ÛáîyYú­UQn¢Ñ?sÁ¨ä+|P¤mv”ÀÙå!L'q1 J"jc'/Ú³¾J¦ðÝuŒvñånìâ/V,*kÆ– È_©±Û„ègöÂçã˜Ó¯¯XN¹ÄêQ 3Èq+ẗe1Þm…˜‹ô%êÁ¨B¢ºÃ`xQß„Lj‹Ÿ 緳;Ľô5ÑØACëÛ{Àã’Ënªï§G0ãÛwP¥0Øsô$0!äf!PxÙLuüŸŸ™³Nc>ÌufŠ—»ÈŸíY…Ûã*å*s\–å’pË ä·ëÇšm´'—Ãú.‘]Pì*<ÚUZ¡ŠÙ\`'›èöþ b"ðD9ù¥+ð5#ðè¸yo6¬{B$0€ŸÔ²¥f\å,ZýàqŠaW¤ÌofƈÓïЭ eOÙfŸý;âxÿ3’˜OÈ"$xŽÔ¹Û.€þÿmp†bX“Þ}‘2œâ>@ß·j?h§íÙü!Ñ?VÜþ¡_Õ´X'ÈõlÝó”ý[öä­ó?kñØóC@è³ìDä³êІß¼û+l>ž’ÉôŸÅU¤}‰jè1ndF­f£ÝËiHŠ"~ˆ ý›D'ƒâ4œ]I·ÔPïÄ0{]I¦ÃÿO!ÒóLgš}Ê«A§}еîôF{3{A5Q²Iþ£Îàã’›Ï'˜ÿò’\®zDZ(´@lÞ­ÌÞ5Ix+}UŘYqˆŽ”á¾…C EåjKf’¯ KiH²(Äßf…­FýªŽÁÞXõ²ƒÖÑ`q”Ì$H÷Éul?»­ ÓÜÃZvÏ_õaÛü<+²ºãm„f¡Mí©Iïv}}rêøèÐT¯óùí›#¡XÿE-HHº»ˆr®®žìåÑnáiœ/çYò¬‡º†ìÉ«Ož|ªìÁ„bòä¾ê™Ç ±Ö‚¿ÿvJɵÐ[u®Éº*ý †ÔÅÃãÝ [í¶Cyù¥OÀF‹Öz)N¸Ã±8ƒ|¹ A¤#u¹x 1ú¯‡\äÂSl"²+Aã)¼¿Ë™}}”mwG@u—êз)v—¤…Ÿ¢Ô4AÚ‘O&ËÇ´EÖY.Ò-_ —c1”dPš@æÊÓ•Õ˜±¨¤õNÇÂþ­wrjÀÝÝcSu Â×sås]lÿG*hºŠZ%ÝÇÈ ƒ1ÝÉ¢üç5D ¨àohÑìÛ“Y"FdÇ÷’‹µ õö« 7j´û)ÖCWCצû¿ÂJ¡í¯–û‰—Dz ÔƒU(Ìx’‰ëÝÏj’0“¹.C•ÜbíÖ­'38ÇDÕö" Äí—Àæ hD;´ôax%&JÿqÄŒV‚2Aƒnœˆ«NåàÑ_™¹ÚÅhÇ „}!"[ý! ’{úà úyE ¢¹o"c©kÿ*‚Gp}”ª L!=2#Á.<ÄLªåÿSs.SS%Á;[`'·ÈwÍ£“¿äR¸ÎP¬…WÓ²—®Søj(ùöv®è¾(‘@ý›Wö°¼NËŽóØ¥‹ç‹6q½ÕRëˆcΡ*”Æúú.1z+r­û«¥Òa€×¢ Âoà‹à;é|-1Dí´ð ÉáÔ™þf¶FKy¸@àç‚|pÚÊÎg+Âtöá·ð«&IºV>d§Ò¼´òGÎ9Íׇ¦uxÈ7sf:{¢Oa4š¡" Ó›70 ºUsNAUƶ0W@9ÌT”XѰOóZÜ‹¦‘´>ËàýõDŒ9ÇѼ×[òˆq÷Ï´£E­D'i‘bÙ( ¸¶’rFîV;–”Ë}ô™ž^Š$ƒ U‹ÉA«¯®©Ùì>»}NkÄœºà íqw¬éò Ø¿G "6eǼÒúiÇv.*ÈMû ôÏ }Õ‰ÆóŸ5†NcïPøßú„@#V®¥ú…)1 ¾Ùë¡‚D{ k'AâiCþ—3q߯oþ´ y¢G¦»Òð¸:Î’ZúÚ”Ü$@µÖ`MŸ¤ÕÁNx.žÍ×¹D˜G8V!xYÜÛ¬[ma¢y3qiôУԻpcdñDfÆ‘Ÿ.ªª‹ˆ†/ñsžÅ®0·¿—4†WõuÔ0Ía}8Ÿû`šä]o¥O¯I%(dƒ´¢$ð$èÏŒ_æ;e(#ïà"v|í £xâ¡õDi|MÕrX€qùëÏ<µÇB­Ý]Û}§´Ò·ðÌ~J¶îJ™ 3âÀñtÇŸ;åðz’8N±¢ùÝúœ7ûQÇ Þ¡§(X&L Ï&}Ç+;Æ S3Yúl'RHÌñ²l[h¶»•ß„z†û₽iϼþ;-'éÊ?ä[L2“›ãLd ‡}ž‘wêÓN“ £DÙ¡ïjí„áùòž1 WQÚõÛ ÀQs¨¤˜™ ò³áøy7‹§òNFûÑ(^ý?¢°€ög…²f@'¡=xÒ%•€ÃŠ(P]«ÙYüÃ'olôû˶Ea ÀØï^l½T£'`±kdzfFƒøê¾Â;ÉËíZ|ÙÌÓAŸØN ƘE¼ŽUx¨’•O­,GÉs2/Ô¿b÷ \?Í“àSº%,=ää$ö¥Ë&ö6¡= ¥I"à¬d|ÉrgÑÿ7Ñ"+Ê^‹bŒ?g@H·Vç&Î:“Ïwh®57£Ì_ Ê“w}¿*RE/ÒqW?,–'ì’{¦ö¯å¯N¾¶V¦¯]c?Ør³–%0iÿr(Å ™•2S9#ä ( ¢¥e Î/åߢûö䥾c ð¡°sò™»%Òë¿OÚþ6³6òæ\»¾Ás^˜±« (²¢ÎøŠ)ÎKkø|]a½#^[7|“ÃÒ-ä²LLêEôÿ;CDpzâÛåö-§J~U¢æ+W™^Ò¸R­ÓáÀí[¤×¥*˜hÒ=jBµ³å¾DÔ^O*‰ ‚k•ùfÅSex»Êõ‚éîž`S€©Í<¦ÂN'IÁèWÞ·±òC2oôF¨:€ZŠ42•øýWÄ…ðƒ•½I9±Ã [Ö/ËÿwA`D8]BzÚ¶§Ðã*òø&<a,¥}Üjç¸bxt½õˆŒíÅ"¾ÏÞµé4ÞÜŸIê{‹•¤ÆÁá'3yœ*º8ðÏMqzë4üÊr‰utÃ'αôéŠ t´.AÉÔhÛ@c°3n*b‡#ú¾"E}n/ ‹7ئhÄœ!fáÃR„òTŠ)í7äôŽ÷ÇœÆzÑD6™@« ²²û:ª½F€Úà߯çH ›GM盘²èï— Êxä­gSOøë„ÁT\Ì«‚­ÏÜHN/‰/¾RW€ƒÑ§D´ ÊÉãùˆÐGϼŸØçá-OM#!î‡Öç ËK½[ÿKih©E”f¥¯Ì `1%‘»=üŸ=o…· á<`c51¹Ã{ì*kà—tN3_ìw=`TžÁ/LI ‚§tÄQÖ€{ñÄH`Þ&…Á±% sØ¢’Ûê ?àÈ@±¬RFÄ(×£®w¦ûeäú¡$ /oÞ‰¹¤ü W š`§‘ìb®‰Ä\â'dÇfòÆïŠÇp Þ¸åÙ0·ÜyÒ­$d§&gkÄQp¨ÙAOg>EÄ€ ×õ-“ø…^°pÙÖýÀn¢Âî#¢Ìø5óN–èçÆ×ŠíÏ›¬êÄŒé…Æ4Áb-½¹mì4ˆ³ƒë.ÅùkÖO¯ƒHŒ‰M#X (Iµcî–'Æ%Çľїú-•Ð÷_§‚ÅqÌ€’«øèù-Jß‘µoòR6³kš–Èë%Ý‚¿ý'ϵ _2L9ú´ÀjªÈŠÉŽ žf@]^cçðÜ¿á»?Ãu†ãeöïoðßßá¾wÛ¾X~ÝÄý»™ûwöí°øY®‚Ê|2™¥@nc÷éEl3Źܹ‚ýDgŸHßO©ÅfîN ‘Ãñf¢.øYé© ɧêÿI7Ghº½<ôØí¦K¤ èT¶‰[i’Z/GH®Hÿ;øD*·‘F]%9®ßâú»bT%NöÌ`ùÿ¦“0\c'ÞÐéÂ1F«õ3AtøB¯354ÌâPˆô²¿¨xãÅ8'ÈP4•Í~Á.äú¿¨½Ð^êYb£o˜ 3péÒVòóÅ!t >ö塪¢2^F‚æ—u‰ƒ>ŽeŤÈšÐí‰XÌàzÉOL{°4ÅÕÔ>†µÇGEQÇ¡X·ÈzÒMe8„Ú›ÈöÓ-£ú5ç(‚‰)CÚoQõo]“ó"„¿Áû“ ºJ‡ó|‚duO%çõPЙd3ú0:î.êÄ€}†·.ÀÄ.ÅÚ]T у¼.¿Ç·g&™~‚»i‘‰`´\?©:l„W!Ò4Gh<«-¶ƒ¶¦ @úQYˆ5ô*¹J²œ@}¦ìr kP ŒS0¥³|[-š=É%6päVèáÃdªkëhäo³õ"nºu0Æ.‚øNµÖnrÒ6la«õðîFï°[ù¾‚v£XƒzÌ=à­ºÍ — ]G®&AªUDfÞ²mv{8;|u9B0}¨]·œÿMü‚|¢™|в¯?œjèeú¯ý@Œ:¡«y•‡†´ñÆÎÚÖ]Ü]ôŒT@Å6Ýçæ¬ô»ä+Âøß&†Š½” &×XìköÈX Ø8­ÊÓîcTâuO‡Úem/|ãÂîáÃæ¡ûêZ¤…÷°HÞqàÆá¿9‰[~4v¦Tgå¿Ö&¡¼MÙ»úblR†á©ÔžÆª­%Ï“ßXTék’òPLiÕê2øMïŒÒÓ7¨êÐ\„ÏLYP¹AÓòNGöðkzψóFúÊϘ {«ÕEù×tÃAOQ|úù óß—ûZ^_«º†„\:¶ò¡öpúšl©xŸõç˺°_A;èðÆm=¸eYÀJ®"ïѬ1&½J/¢ËV¾¿¥€‡£m®ù_½¶ö=ðƒk„þiö‚‚¸¼ü†i¾íÐyÑô_ s€âõ}Ïž‡í¹Xo‹¹7j_Yþ’æx æd¿€måOœ“œ¼Ê0ºË×ÅW³ûjã¡_€/À?äkŸ^\šf¶ó¦MœÏ®?÷€8Â%‡Xq0åÐÁ-“l þÉ ã ý0µ][‰ïÚ¾’œ-cÝõŽ^#zûÓš]ÛÏå™Óå²WˆZ´êæõ¿Ã(ðòÒ_”QoêI…¥$5]f…;É2ê%Y-f–‘¡5ףΪ˜Îè†Â1„L©¾èIkýÎŒè«@¦úÁb† 6äÃ%E³ÏIƒ}Ë©ÿ.üc‹ÊË;™ûѧyXŒÚ>ù w_¸6®Öû€¿8W'¿¯ƒ$Où™ý…ÈŽdªÇéNJ±ÒUoI>Å=z÷IÝ"TñËMgªícø|ïÀú3òEŒt­™Õ~a\î™è0£-ÐÆY¹ßìÌyž‡xXN¦A#ðÏÇ™¨U]5/(Çž‘}Pûá™iw·çÄá{DºHðm"²P¥>ô!Î9$Y“ÿb-ƒsä>ýžA‡íYm‚¹ês=\í)v,/œÆ˜½5ƒyËz”Sã_¢_µc'±/”Ýll}›)a’R¦mBÒh˜3zdÿao•8ÿSüê2¶úwã{:ž~µ`ÔLêD töfrB§Õ”'maN^:›2%ûHU§ôf!n©o¼j={¨Ô,É¢LÕ…¶ì8z¦ÊDmK—}º¿ÓëDÔ ƒ1u ¤w'»ªW³ŸÙPà×o½¿$ÀÍRPôJ“ÏÐ =ùÒ’9¶–£x¬  gV^lñRz%;·@Ÿ_þ^ /û ˜ÖÕ{êálƒd€k«&HY—B¢K›!ßq³ôÅr¯qªbvÕuhn ª‘Á0\ÿ*.A†imò¼çþ¸—øåì4CáÍWÛz½×}tIIP’¡HJ¢oqŒS¨P+4·‰\ wp ÄS~=ÛTJxö\ì鈟ÿ êªp¶“°ÿQè¨U€jÑî N”4Œç×iÜUIÙæóz!Ÿ·ò+¼)°(1ŒÍFðdl=‡¬UHG"̡܇NÈ­–9 #þ#%Á£Í!Suƒ“ <ì¶h©Áº.Dí=xlº»Âïðë‹ o!æÖ#móÜoN5E¥ Ès­ TüË ±loÑçëUþ%ýÌÆy‚Ò_…eÀ6ëæ+˜kŸÿ"ö4q*°wD`5æ2õþÙA¬§v)Ý'aÇA~Ièò*pŸÓв€ÅyÃce?Vh¥õõxÅÐðì.5Y¾N•—v’†]Ôao+£Í/3@*ÚùCÛçq(ÈøålD’Ä͹$ºËZµøVVþ¢DðþÉaUÂü¯Þz†+ËlKv8S:ñ¤O«¥M^‘BäÒ€òŒmdÑ/†¤åõîÁšHÝÕ™-÷S5é34ﯗFͨT;cËA¡@CLGt'êVPUupËÍ-}Ví÷’? ~»~_LšÞhýoû¢ñÔ—V¯+ˆ¥à¨m챜9V¸.Øáv Úk…r¼ïW²áf'ž1â¨óN|rÿ)Òà±Lé°IøP*®1e.­ûêô¢'ËÈëò½Šé\­cs…ÿ$xV–wlR38üÕ!QÞTÏÌF6ëÏܬ:Ø}á{ÏLÞ;x¯¿Z0W,ªý¿»Ñ ª÷Øj"‡ÛâVChÆI¡+mJ7,ðæUh³®†ƒy‹µÿ5ÎæDèvê)Ÿ¨FZ²C¦žhRŠÀnŒ°|j}*—b{`e=Ü[+›p›¼4µ?c×[' ‰´¯4žõx¢¡>7¨OCX “-®/ýRNYýÉ7-¾–Ú1YëoÕE#j,›hkïF$’>ÈîÞÖ5nS—@y-d_½V¡2ÊmDŠC±?Pµ³Ìe2Þ«Ó"xØMhpÁÁ0?ºÜ ¤Á€¢SWYô-ã§[Jh:nÃÖÞ´=Š„ÜörþÚ“WÖÈ“äô)'i#«Q'´ú€í:'“*¼YŒ6WÒ§+¥>¤ûlÉÌ0þ<ƒï/ø˜í…²ï|x LòÎ|+þÖF\âÖR]³ï˧ê»Ð‡bìDF‚:Xï«çP Ö–ýþ—u÷üJ 9z˜€$À×Yò4d1ûŠzó-[Åî‘ý}nÑg®_Ï'Âa ÛÛž˜ï‘ÌÑà,Ë“wøõÛf-Mó¬å¬åZ/! cO£T\” kËJÈ%Ùâ݈vB ©ÇC»ÙnÝìït!¾!Š•¦ Ág’"å¥'˜¹ÿlÑ_t‹Ó➺ª@{4Êûñ.¨¼m !BÒÙçx|ț ç\í{‚å¶Ñ‹Åð’äîxléwñ³ LÖ;¤¼M¿Þ ЪöëÀŒ(Ç­8å;´­(›¾¨ëFPçºÖ:úÓ2=ÆÎžÐ$.8²Í)ÉÆP6)4¤D{AÚK­ÎE„ý©Ì~m‡§•À]°l5”UÙº‹^Nß¶~Sê[Ll O¯ÎRˆ„0.›XÑ Vê…LÚÉβ¸[C»¸3}}qC:^Ö#¦~åæ*•:ô8vÿ/.¸ôAù•~ý¿¨< àÛ]·FìQËü3ndÒøaˆ…êüx dÕ¾æ„d–+2Ñ¢™·î¬J*£ì GÅõ÷lîV@ü_/eq ½Rpš;Ò« +%^ÈJ% =½8‹wno4&‚*[“oH?\?¬¬ÒpÞØ>}Æ D5üÓ÷SHGm{Ù°vŠy¡ÔÜËV‘Ù¾£ÀÚB‹€¨89jƒHeO/A¸i^r¢f- +»¦@Š?Ð+Y ˜¥9І²ØÏUéq"‚T¥ìwwdøî·‘z¯§hnT—^€"ÆX³É!Ê~âr¼Øé½%²bN9æéÃ>ҤŠé„I<јÆ ÿ!9*Ï~‡¹{ê…ÿnÝ/ Xà>m=²ØÆã5jÚć’ k÷…Ÿƨù>Pû•È,!À;rfî£]³<²‰Åø—~'GåÊ"©/}å<¯£µ3˜[Îä³Sü{4lå^oæ÷iØÎÌc'f!øÖŸÖ#M6íq~¨…Ñ?âá´Øžöçá=qÈðfBŽmf4ëàœ6é®gþÒ/z9ª’­$+Û [Žd^Û6jo8.è$uõX e^ð°´„gG™­-4RÏ<Ç1­Úñò\Íj‹îî[߬rD뎮ÍTæd¬lÿW_E<¸æ¼XhTÕgp=må€4­\£ë­ºTô„J5¸w‚x ­§Vþ=Œ­)OÏ`h*ìÑåjÞ¶œr:b:=ðí9æZŒ±ØˆbOö¦NàtÛTjÛÐ:Î73Ru"†!ï 1YÖÒ¼~H蜮ãN¾¡ûÏc‡•oÖéáѸióY ½¡« Š–Á0Q0𾙦e@Lñ+Оwñf3ñÖmÞü_0ä îw\ÀÍÏÕ=bI7¡Itpj cåíÄjº›Ø5¬Êl£åJ9Ï„X•ªY‘ Þ(pjþí*˱Ö/©Aµ&Ñm©gÚ.‰H°©3ÃQ•¥ ¬þ½†²¶N$uñ÷ç;fˆŠúåãè ‡ë ­ îF@øYX÷qþ£%Fø™Nú¥7ƒ0Ž7/”£{yX^B²%½L¡mWüƒ!’ÈV±•l$W‹-éeî™üþJ;–•œà³Z¡™PŸ¨<Ê(0ã}r}æ›ÆòLDÁ…J‰{“ðìTjfÃñŽù² …ÑLã”ã¤%³ O9 œú-T8~Lq¡O¿A,ââPä·]TG)ïÙ,áV¡…§„”Ûýù§è˜³Äã˜Ò’ÃH"á‡÷»ƒõSê/£uQ ™´ ‹Þ %ÀüÒ±O>er… —å«WFå(Rºõ;gׯ85 ú|55ÿ{"vÀ¿9.\KÙ§×7íƒò§fÊÕŽLœßÍa6UŽ_©´„\Ñ’/2ÊÔS¬a8Œ³h5hM*¡˜¯‚‘)@ýDœ¯OwŒØw=!Ü­P¥î™•¢ø%® •Si‘v€ª†rNlÝ™éO! ¿6o!`5éCµÍ¸rÚm“œy²ðïÌ#Cîv¤Ú¿«;Y¹ÜгF¸”XN¨Ókß(”äÑØ®€^ÕÖöroÆ”‰šëÜ+yô_…7Å;Á o™ñBd…VV”ÑÃTX\£“ÕLÂ"}zÛ­*Ðk :QMCú¸çÍÕ0¼k 5ìÿX „‡ÈmfCÔâ «¡}Ðp‘~‹KØÚ?[_¢N‘lA;S1.7N oŽÙ»Ìv) T^ñ¨@‚<Ç”œxúÌJ+8à#ƒ÷ßËg÷cb¾v+½‘¹™4TöËH·¨µ¶ƒ‘<æýÁag¾\šfë6ï# ÿ €FôznÖ‹ä^9ã $ÚkàÍÃÝfƒ€fC+Ÿv¨©®£\ùÐö?…ãˆ!&O­íL¹øRƒ¸ç°YÖK#Õî}Ò{:ÏdíÊ3’BSA¤,€/… Dô¯“Ë J¸3t(Ü¥¬,ý§mš!Cîøú‰‹ÎÅJGã¿ûY´‚5ƒr¸ë&î2A¬o ]·Ê°/ê‹.EßÔ‚£¦Â‘6Ö!yÉ 0Yˬõ¿^2v4æu0Á±2>2zŠ ÑÞqSÆ&Ç\ÜíK»©ñ‘„ŒàûÚVÿBšŒ‡‰†6ãZú˜J”åN@Öøærz@nÈÐ-ÐÓRÏÖ$¤­ #öÖG¥ ŒÊvl9[Ƕ;þĘNj[uq¬ø¯ÂÅPáÌüä.¤·dù/dÐÚþŽ›eçy‡ØÈ>ý¿îî‹#!¸PG—%¬¥/&oóÓÂ[ƒÎç‰älÒƒtT+ª!^SI©9Û•?¡Lû©ÎÃtÕ·¸Àb{›ÎÇû¢N¬—ÅR9¯½ñ*§­n tž‹+Þ–#àíŽ/S*½nß²y‘žiÌ–ðm¬®ù ÆnÃnþm˜™®9áZs–Í'û a'£êtðCÒI¸Ù. CsõômÁÉ* Á1)ñ‰¢LÌZÁcaÙ÷ý]ÓêØ`r õgS‘哎EåСPœp-c(ç³Ý ñÔ4ÆT{Ê×[þÝû 3ñdbV%€%A¼·~è^|¬Ý3°æ¾Õ ܇8X¥@Ã˰qïY à°Ô£´Õ±E…rW5Cåÿm,Ï(áoR“9;±.Êèh:r™Ü¦%âhlTÀXÙI‰¹r@F²ÏŸqkÀµ9A}ê°¢õ™ çxH]îorHk™ ߀AöŠÓE Á \c†ÕÅZ”KƒêÌHøuÙp_)'”Ö ³þ†•»%bçßeTþÆQ€Ò¾'\±y5š„E Š4]ñÍæAÌ â9؇Å ô„kõÌ(d+ CaÔç· Üž×šžZþ-Y‹ˆï@­Ã¾gšœ§À{Ñ,¦ÏÁd…+qR÷sk4vÈjÄUÛ±E\Š*6eõ\ÕâN|²µÏÂîPf.Nô@²u†È˜»Í,úªë š’]G·ß lÓ¹B§ÈçþÔŶƒrÊ…“a)À©'‡„¸;;;ô—J¾›xëx+>nŠîÇ2ô†Äà˜ñÕ±M«ƒ¯ìŽ[Øä}òÒ„k@T©m&§ÌðãQiô¬Ê'ïAó¥_pÊÛÊ’” è9—,b¶ƒC©¸É8+[ÆÀÅyx gøÚ.ƒ…íÔ×.!©e´_e¢>·[ – bª (>eQVú33œiyqR¥sÅä ú¥+zÈž£ü" Tt$ ôðTP4Ⱥ„˜€2Y¸3tÇáYÇáWÇç^e›Ë?Mp¸ê*©}KLÝxá¦ô·k;¡ðLk*>µf¸„S&ÜeöìŸþØÉ)ˆ¹šZ&O¬uÕ38ÞèÒI„ qsƒæ¯°Nm²—6·=&4g‹Æ¢Ù}8yŸšêˆ$,”¥»ê×Õà@ „<¼—qsV]1ã™ÿR -¥aî‹´[¹GÙÇáNGáKÃ𧀎+PO£xÎûd½ÿ^çÑ»ª;ì×ì¯%”c¬›Î¾¾è»ÎE¿Z^ßa4$W5:ªIØ»æÿ ýÓC5bstÚ‡‹@·Áå?Ü·i%XŒ€‡i7ð·rï$mÕ)‚ô/Cf¦äj¦2⎞ûÿ#ëþæ0åjZçÚVWcQ<ÀÕŠ„xn«P›¼Œ˜UE…+äßíP˜7¾3VîçÒ\š©›ý¬¦ä .ܰk{‚¹N«¤¬º¸ãj[²\HOý¶€wÞ`O‘¢ÛÉj5¼í)ä ?ÕÍÄÂÊ ÜéCçHZr€ðdBÿ€½*Vln¢&]2œöÀ„ñBH,›`cÚŒ˜H árjº"¢Ì©~Î&lbu(¡—†5AŸæêÃãG„táX}ŽÚŸÅ ¾Å1ee™A‰‚n#JLý_zÏxûÙ®éàTãyòg ½„}Td½[Õ4*-ö&ˆ>ŠTì{ÙëÏŸÛœ!se– J¿?lª½ „p–1c==zP$wE,eNÃíZÁZ‚Ø„^›“îT9É4VËœ ‡&¯óÚ+Ñ«õ_¿ªÑ/[ƒ“ ˜ £È·ÊÂÀý›d^I>¬²;©½l&2€”E.ß{Û(ŒèLéÄ“zš@®/ ~ŠTz2£Ó˜˜ 6ùüf§¡;pŸ5˺xþ*œÉ6=½²¿eÉØ*‰‚Œ¹ªBh+iQ¼·¹×fy ‹æ¤Á`ºxž_È7+›¹r€›+¨MNBýá›®ô¢àŠL,ÎÏ©jvH/o–ì}¼l“k°÷ä9‚jB¨­ü§ÚÙÀôܶmar$ÓÐhææ;®Õ'—½‹sÄ‹KV¶à{·-ض>@ bª/'¥œxnÑK±ðï@.Ehƒs|] xõBŸ0]$ñ0í“wóÓ­ÿ–ç!@ïsC¿=ÕŽóÝ@Çù§6Õ¶q:êzWøV#à•ŸÞ"isõ:²[ú“"¿Ô¨¦š_2eKßš¬ÉvqBíšÑîO׸B¬ÑíŸ@Y|-Àt ¸ôrU‘Äȼ,8¶.Kz<< ,)AÃøÄx£lŽ}¤Ä&Í«¬LüÍ©%ÝÛ¿e{õh´h|è—ÏÏ¡Yùô%ŸE ²áU¿Ã¡+…báX¾›O‚Ï» ½)Ö¨ºVIÖËk€BšÎ]=¤¯Œjš‹ÎŠÍ\}†Ž\J!ý¸œ‹Ý1%¤lO{KyzéXäñÖŠ«;ýâ“7ß]”5áU– ®äPÖaËt„\ÿT:yc5Âg-“ÀƒHQ‡*H)/DF4cÐV‘bÅ?x/ª#)î`qØÎ>hÌ1¤6Ú¿Ãà¹O–b±„£ô¬gþEñ¶ЕµYÃùZ\™ñÁ½ùÜ ¤ÇÂSNR¹—Z°}-G­éɶX¤«hH¯õ6wÝ€5ùˆwìëÇTÛb Ѷg¾¼ß\‰,8üÚðwd-¸Œ\û'®ªÌyë”/“%@– wߪt׎îÛ£ÔI’ùÉ'¼¹f¥lõ£OÝÄ`tá“/åΡ¡ÿ (ƒ ⣟ÒÑÞrT´x3j†=“3ã€syH¹#OÉÔI·xyù•sþ·öÍœµ¨¤ì¥ˆ³d¹{Õ¨KˆR’`f›às+X²ŸQ%Qì6ÞåcxÅšT7xâL5ÏZâÕƒç|¨Ðƒ½V3d%Þ×ÿrÄ›ƒ€À9TÄjÚ=ØŽž>äz)xKi; Šønt«Cݶ|àûˆ ;´ò¨¬4Bc!ÆšJ¢Š¥Àô7)Äc–ë(‹òý~„7åC•ƒ‰rL¡1—Õô”y9¥µQ³²Šœ†èþóøt@þSéj”¨I€“ÖÐ+Šý2جÜaÀ“ÅRøª4Àƒ„¯DãQ7¡ÏLž K›¤)Û8­óî¥ öµÔE×¼,ÿ<T$ØÛ#‘Ëåìf\îçm¿(ƒ/t @,$‘\½ƒÇõúÒvʃœn)^£ ±ó·m߬óªmÙoµÐ3ZãÍC°rTnó¥}nÄ–yXÜN¢K[5Ôiº ž‚ÇßOãe4Ð&¬ÙÍÒ&¾«¾ZEñ?hSU:€°äï~PäfÓ©}ìVïƒÍ1dèŠ`w²¶Iý{­šÝ—n±ø`Açž\Lˆ¨Ø~Â:i¤Šñsvy’¹Öø`ÙüE˜¡–ënê?R¯Ån¨âóKsoÊ©_[fãi  ŽF·}t ‰Erƒ4) tiÙR8¹S¬Ûv“uî$ù—ͱ^²’IËé'5…еWL½*ídü"`9«ªþH,"p. ­íÈCÀuõX‚… Z,áãx0 ;5’-ÃÑðé®KÇ£ÚØ˜ºšàïhVI$38Öî~Üÿ,„CéJqða²ʖ³]e¬îÞ#Õ$‰ ìˆ-QòÜ®À@™¸Tñâ<ÍV6‹!Aì[q¢‹ë–6€ý'æ=Î~S›Ôϼ¦SÿéCI»´YTD »–ÀëwÖ<)ÑÜxúÌòˆëÅÀ†‰jÚ´Ž¶¨³àyýÞi·1›ÓsÕH±#¢Šµ¿ðì.±øªü“aî’<гämÆryŠÑr|­ J6O[ š"‰Ã¶ÕÏ2éxÒUèºä*ë”ôyƒØh ÜÕ¼áˆÞÌéÃíØ¸}»Ãe€²ö‚àü’/X{6gmëݹ(¤58¸ïx–µßûX…&šÝü2D+näÒú†sŸçOҋɰËú2PaXúžÑ~¿V[}.XË:’ˆew†3çL›–µÌ6´·”«3¬^²3£Æ<¡Œ‚ûæõÜÇ⡉ŽG¼l( Ø„ J-z&Ð_4Ë×3¿>° À jˆùÖø›Žyp8Š­#Â&Æ¢vjÂ64Ö Î«¡ºE‰®.Ðׇ‚e(å¸|»äŽ?#—Z©¶´pÎVJ¢3œÈû¿\<ª›j™Gùmó >?²æ#Ù×rð™ÎDY7Jl‡†ƒÖ;j3åì "ywi$2—rA‰ì—C –]Ú|ºQÙ6ûøŽÞK5#CÇ “Dtùä}$ÈÏß' ”µà´¸ÓöÑ !¯IóD\G,Èqs!CðÍQ߆kÂ/I¶É„³*M˜\{º Ý¡ßÅœ©½ô]NýGeè­S¯\í2N‘n‰ ƒ¡ÌJÆ8Ïú ŒCã³Üšz0@ãZj |=™ç¢úÈHÈêÚÇ.mpþNi {.LØaäáe2”Œ ãtZfî%û‚(TßtúpPå‚;Úr_nÙzhÅ+ÀG¬Ÿþ«7ÚèËÒW²_5÷?*¶P¹!£vfvÚP4¬^Ì †@«þª§Æ¬;aÒŠGpÓ¾¯ŸFlúYöñÊxq¶9/QZn,ðˆ„ß !*¢ú϶L›Ó«³º «ˆH¡­8Í„JóX)½Ø ½©ûñC  ¼†ZÏ-e×]l«·ÀÐ×/tÕ”6Û(ÙtåDÉÀ†m+~½H]O"½5]©'Ü ²:‘ãõ=Ãr ‚Yªj ˜Æñ°Ãžð‰ßV]A¸Ç¨Fy¶7.ºÙ©>‡3²ôé=ùŠxÍro…îá5Íp%°yõ²®E&m»°Éƒqh)X6‡CÁY+áÏ`xû“¼}âN±|@@õØe™ðþ¡šËž@ ‘Ö#.àZ¶—Å‚RÄYßÛâ„ ,[÷ù+„s7¹~mþ/Š´Œ?PŽ0R%3ÿf­Km*…àÏò“ÕàW•»@Î04-´7‰Â•€\m3üT¢)ÚËQfÜtþ”²3Û»]ß‚9cC³ú\¤Â~eü-3_ñ%/¹Î± &¶ ¿è­þp†}!"^ŒÞƒù›rž÷Z¨Sqe`ÉuÍ Aãqµ»Ex›eqÐ0ˆ¢J >÷-Hô6²Ù5q OÃÈ¿ÿÞMݿ΂N[ßw¿žrÑôêÀx¡w-DT±FJ›KÆ%Ï‹¸Î8#Öߚ»M½K¯Z?Ϩ³PmÛa\!À´xåç¢ïðd,:¾ ›}¥õW¾@Ò« 8^-Œ¶E.̸öT Ò&*´]Éœ­dàû5 Vdý{û ‹ÕÓP¤”ϦÖ(ñŰ~ÈS|+Ø);l$Ãí·#ð¶aø\ À¹³ßH 'W B ø`7 Æ=AŒÎpõÔ7à!ÛÓ¢ûËo»¼µÓ–ž¼[ôdõ“®‡¯Ûã†o‡îÂýml7O³g2Ôp#GH€PŸ¹¦¹|—ÕÝœ·TŠfw¸Hº7„P0þoæ+Amoñ1rê)|3<3ƒ«sËý ‡xE.› ]ìÝ&:¾‘Qƒ}+«Ë0¶öP…ãÆƒ œ9Ö&k‡§*>Gæ<0y&JÁΩ®fÏr›_ÊIbåþôP„NN´)CH*ŽÊµÒó7xñ%澇}ìÂèÔÅ&–°»í”í¤{+rMa÷DO×Á1ªb4vÚ˜4ø¼Ž§>›NÐ fÜ2迟5Öåßô§’fD™åÝGÇ[A‰…vM¦‡ó™ý;FŸš]rS›®ƒ(S_yü5â„6ËXmdF³[xÖªzý(æ®ÈAÝž‹‘O9Ðz<¦áÚÕÇ=s? ÃA¾+¯6 S6Ûæ„°îÚ¼•§®âÔ'J8M7óDäüÆuƒ¬¢Ž~šga˜-¥ô8wïU¬ˆ.²Ìfp d)·€.<Î;ÁÈuU=-3-’v碳¿„?åZ¬sªåÓ«cîþ†2žë†ƒ0@U#·Â¡{®î»LsÙ1ah¡\xá(>yvq«¥_’£ÑzÐvR¹µî#>´Rׯ[{©£Ÿ¡»C¢—-u%}‰6$ÿo˜Örþ9À0pI¥Ò‘ŠËñ[×›€y¢ä…!)öáKvAu9,)@ @‰{˜€#ÿ{ý^•^_;‰J]ìZ¹‰ƒ’Ùß3†?õšƒä@XAhQÜQÏjûîS®{ Iù’QéÅä²| ÏÏyçç»±ùï€Ôÿ0ÁÐ —6/ræ´ô”]±È|lòõÜðåÏû¼ž"ntÚ v£> ~aÉ€þÇ}:MËë L¨,§©±4|›\å_Êß”Nä5Êô!,ʼnÑeƒWI¬Òü÷om:øÂÇN²?«Ø,£|€/–^$€Þ‡c^E]„¾>¼8EDaæ@Iš­¥£O¥ë¾r` /ª®éOæ•÷¸Œƒ€ëAŠzÔÝËËm·å™¼\Ô~7'îYøk–9И†œ‰èÑtàº.Ü ªÅ1¨Xé¦Íçñ ›ÔââÂZb¡+Pr>ÈY£!B±eîÂoCzUòIÄ~ë»_ݧËǽ†ÉÀ‹aæð©w`¿²LI±Ð[‘4ĺÇ”ÀìÔ½±Ù`'J²_íezøÙÅÈû©Â½l Ñ¿#²øƒ›iq^ ê³\ê1¬*«l´ê2YLÁлXJVø6.ôV{¹‹ÓÖ_?Ö¥`Wz‚øÿ#p8ÁmÚ›™Ï´.ûönÓ/ SkLŠu-Ú©üô!Ü+¿¯½¹~ˆ’‰™£ª6ÏTåžÒóí'BÁmÀdˆxˆÊw#²»ÈOÐ8h:>± Œo«Ué™®6ïZÒVýÝ)Ä@–d>…·3`@Z¥á©É^Ȫû§¸äǵQPGç8°}€±)_ß°9Ë2 D®rŒŽÕàÈ`‚viÜ— Œê%/GÕ[þ0@ïCð„·6r$Ú7Úûý©±ñÉÐ{ÎAEŽ’šEšˆ%ÛCE D*Æq¬Íòƒý«Šjpæµù“УWžg¢kuP‰ëùÌ®<É_ÝÕšöyãO߸ûP³£À%ÿB¨'1ìÇM®Ù$9ì!wséFGà̯ÞÞé—´ é½‰HBà–]05( «&ªù™¹ÓXì!oÜPˆÆ¯Ù”ùÁö¡æÐõ ÒA)Ò”šL1ĘWúO†"Ú)®öºÇ b ¹¹*BW@³æ¯„õv ‹÷\‡¼šu³¸µÖV1¯'qAyÝ6Ì6 >‹4-dô¼Öê»Tž@%P“âº-°tOwèû1ßQ˜É#BªÏÛ×÷è¤,Á¦Ðõ›ƒ—2Fëûœúµ0mÓ1N§ÊH$|-}eÐ…‹1Ey,¾žˆ’A›wïLIŽ 2øÝXç¶^Ö`ïÆbOÕýM.OÞÔÄZ¤•Ÿºÿ`«›$ßj¦ÝçpÉççñjz”Μ\¯tÊÜ ì‡_Ï:AHMnìÑ:³Ø*¥WHqÃ2BJxê™·J»‘&e±É&’L#$½E2i3Qc\ªc·+Ÿ0ñ‹ªÃ¿[*Š b1ý±¦¦™ýŸý6·}j’œúd»1û:d˜ %ÒɶøÌš0ÆK(ŒRç"\çìªô{ l árÌ ~{¸…¿upAö o¥EgÃg˜¦¦Ù¹'?‚Çö%Ή¯J–Lh£l¬0€0qGˆú¾"½ã˜>s¥ìÕ¹æý¾c°Ÿ.V35•~µÁu§–-ÜøœÕï‰;dÉd%#YˆAÆŸþnPþje¼ÊÚf@+-nŽŠ6nû~æÄC=ägRðzÛS^*¡ëŸ€SD[ôƱ—nò…•¯ð‡ùCà GVÔ¿ÛW3‘ããÉÉJºÔgc‹KAÆ6„˚;*”¿‘ÇCy2E-fG„|;’=Õh´‹©y;õÑßs®6/Þ)ÜuiMiJÙ•i¶ÄAnÌ<î$u!ÍžŠªS *–­) otu(ðÅ+ »W§!(ªÌtQ×S¼Ý¬ø$á\m6À8€Ô^×õwï}| [FVîKodÏÏÔÊ~~¥ñùú”ð!îdº)/'¿ŸÒxù«Žd/§+ÑÓ™BªƒéÍÜ}¾¸6ÌRöQì/Œú‘z‹ëØrŸ5¶îÏââbÍèNœ½9Oìù*UÚŠâøLÛCÜ9¨5Õ0ï<Ýû(EívÚ""{zˆt…b:æi}élô0¤ßôä°]W«)ô~ )í€f™ ¥¾šU?Œ¥rq‰™Íh I±ŒsWL,uÀÔ«‘¸¾ºìëÜý½nYÙ¡¦¶¿þ»ù6þ31)“h‚% ­M´·JÛ&³Òž6KÃ{ÉήôRo.…NÄà’e=w+tãÓzì%thŠ#Õû[ËÏæöóLF&…*!~P¶†Ü‘ À kàÔÛ°Žæsº°kìz~0?üióp1}K;"õвè!}¤·Ö–º]Ö8³kÎ ëQInñÏë‹ÃËê^OA‹”­~–4wë«F´"ó'„Ö·b›×7]ôõ4´dÈŠ*¿ËXĽO6µœkrzsÄ"©½ûœQ ãø¿8Çõ¼N i—ˆ9-é½}?1Öþ¬Ûè·%_6´&4•Ürã:!ngÌ͇ ‰6UéµøÐCÿd›N]zš¨œÙ‚¶è͸àŸŠý‹iô3`ò1£XÆâÅHΞYè0–Ú;å²ß©ÓçS¾–{Áà—«>°<«eÍÝñ+çºÃŠÞ¶ÞŠ4›êhj…ÄŒ+yo¶›/öêHñ耺™cs•9Ϻ€°¯ÇV7¹ÇÒÉxþlš¼¿èVøµ¦¾ëÝ'pÿ\å˜sB‡ù/ãTÜ3äo”‘._»äɳqëÙô £N³h+Í µ ¡÷Ûj3„íò~øéfßÚØ(“F“${ï{F°Æ|CÀ1¨2íÂô.%‹Óóô"UvFvãjövÀØYk^ŸÎÜðÃÃòã9êˆ\Â0t¶éŠòUz¥ÙJ~šÅª,®¥eÛ‹«Q…¥Rˆf¡ýä‚IrnÞíF³ùx Š’”j³³-s]|ýFUèX¹Õ _X_5—2 Ÿôg’$brµ}ã U/9¾Z3ÃÿW 6Ô0e¿ª.€h¿qóüIœÌ?`îžû¯‘¨œ_b8;ûßmÈ,W ƒÛ‡b¥ Û’nšDN20¡û(¹oîɈ‹É®ýŒ¹èrø2âb˜n¡]¾}RçFÓøÚãïÃç7U ÐDª@ ½.‡ÞîÅÞqÑ– )é(¹íçHBS8 ’¦h>M¥‹ÿ8Ö).àØÇdÇö#(d*­È„”à'¸îrAt²&kÿ4a¤fxÝ(Ücâ¹6Çfª9Ï@«&£¤E PA­j;lÍnIKÂÒ¥~p×·Åë1v·GÇÞ]ûUkÉ-~cÌ[ß/<Þk6DÁ*ÞíŠö%¸Þç¡ôì²t‚YÿH†:Bðö׳d¿8 Ã?·C ^ú‡i]AíÀ*x«µ¨ç`ÂNDš>‰j´ta£*ápëWD×ùêc»,˜Æ±R¥€ès¹uguör"(­c%–I½aö›®¹­êª€2ù²¨«øŽOçóó,#¤h µ¢r&/›Íõ~F>]Ñãl|Тi˜0áú;ŠKéîÝEÊߘÌÓë‰mNÇÔcZŽ#m£Oç"Utà½Ü!t¯ò'Œ\žÕ·ö|gÍÀa×®ÖQ63p½-¬…¾ÈÇÌËB ‰°ÅÜ Ò(çùeWɰÉò䨸ž78ŠÝjEKÊ6*aȾk©o¯ë°¶5'¦&'›YÃÒ‘h^m!ÇçcW”<Öf6jDž~·¤¦uÍ£12’F4÷½#H‰=Æ{±à¬u`'$WgSîXMål HŽRk›©hnPÄ/›½Ù›³c‰ÑïúB>P¨á”Ÿ¯EkŒŠÊAÁ¸GßÌ›ARS_Û_ÿFEäÍÀXX¤ÓIì"œÑ¤5CY³ v„ 浡¸³¦"—ÙJµ›Ái/Å»ŸìÖ7Ä9‡¾€û°vÉã¥Uné7?9íœ7ƒ£Ý›ìQ®êûr'¨¤œ—hWØ>ÙäÏqR©5~:Ðs#цuEt¯L+!÷V ŽÖGFh|4†+÷òì9 M<ÃVbuÏ@0cãTèì‹M= ³V>'C'ÖNÉdg ÛK|ó²ñ‹žFÏòÄÕ6‡þ—¹Zœ]4c00°˜®òÝg'´ |'þ¾5ƒŒ\k*o­ö­61HÃ!=Û*Bh4ŽxB/“~Üy˘µË‚“­`¿›š¬Ž>킪•Tí¹òcîËc¨²¨œk½ñ9CHOa>­®F´^ºh‹s¤äL¬fÒ}‡Ï.þ6"4QÅê PÝ)ÕšŽ±¥g£*Þ­3WuDÉÚÙ;2ÂÚ¹1'˶Âí#  C„È}y½ò-tšdçLæÞ¹¤Þ X$a±ý%H3åH¥ëŒ,[E+Ú¢-Ê¡1} skð«ñª¤Ö¨&ºHÕa½™äq.‘SÜ©ó­a!ÝðÎË?2^T…¥¿‰ÕÑ^·s¶zùò¡L ´Ùüú³gg(Ta—äî¶PvLêVHv<ê¡óki Ç´z8‰Î[6å²Q–û »¼“æYacÉ6òr°éȾ>ïv$Ý¡6'•Ý¢¹\8¨îožUùüå\÷¬™~ É®ì0Ug¡¨·ù­ÓÈ}“2¾*tn°¶ JgG+;µÕ”U?²h»ŠVÛtví7Ë¥,Œµj`da×àGçµÏs¯mƒ©~øÆù E …4|ªÌ×V~8cÜ3«¥ŽbªH®•Î9¬îìÿ„ó¬®åƒþ]ƦÏ| AZ·XÎ8k¸F‹ûèÊ›Þñb8Ê/ÚF"=yÜþØøb-ш*Ö†Ùä9Ê¿ 8OV4+†ú½éOê1›ûÓ2ò¬D¢êZÕ£ì ü‚ÍN›\:ëjØòfÃAŠFœðä±ËÜ8üÊôÓ® %+-rðýOCÏÞV9ÚoÙC©»J7oÈÀƒ˜AÖÖ›Žší¨™è}Fü'½ƒ'ˈx§ÓFú’ q9±àÈ!1Ü·¾€yÔŠ70 •ž£ÒÌžá!jò‘»,ó&úà%š Ü=»¡S„iqhE?e®ñïpÒª¯Ã¶±þ–ê ×~:¯Aêë©È6Ì•ôÔâuRb¼(fexx³ÈïN’£ŽÞ' …<…Ç¿i8y®ôøÃ“ ²»B‚97™¤‘˜>7Ü|ïŠ7syÏ%m):/¢49Ía&ÅÖÿ*V²¦¦õÿlÑ ák©á¬²» Ý%“³êVøIdzÁÓf³Ë–p®i8Õ©«HÆÑk¤ž)$Ñ8Is:GE­Ý¦ø¿# ïà ÄEãJdì»wœk}D›¼ÔWòõ3€Åá¿í&™¥™)GK üm5w_§rš^õ7 òÇyÀIÒÂ%µ”ÀŒVœ½ks D¬t~¡ µôÍøÊ­\˜"½J]j+î¸Ó>ê{Ý Útœ¼k¨%Þ7%Àtžàmþx†Á;ÎY‚!jIÌÁÄÈävüÔKôÃ4l©×r¶¹ó@Ö BWÿrÄÁI š$ĹöÁ¯:Yûž¢"|#ÔüHVmÐÓÚ¾®:W9M7ƒ tÁ÷ÂKüšÂ{¦ãшV‚Q6Y>í~“Ö$µB3& _öÉãn@Á ŠCÓ¡ósÒw1¨#Ç4O/Ÿ'8¹µmŒñy;þ£Ôú 4š¾o!-¤Žu° ¡DNs c±5¨ÇSÑ”É×ÏE,§¸ÆöÕjU„× ¬¾ÎÊÛ‰óLš ýW/ã{˜‡LÚWßëÞJAGçÄ"ÞÐî˜wíŠWÊÃ-€T+y‚zE½«>Âûæ‹;ðbx *b—Z·öOÉF^Å OD­pÀvæi$¡:‹0ÝMÖŸŽ)‰'dBÊ”VÌV㽄˜Ï)ÜìA³— "ectøòê¹ÔÉø¡1ç;aû§8„KèÄ@&ß ôX…-½=Is­üÈP2ö³'>_EpÄsþ aÂ</2÷ÙŽ{`þ¬Ç0Z½¶¾q@s­ÿ!•H¢•l?qØå¡2"…UkŽƒâ]ˆY7zÓÅ6X–èб¤a9ì¢+ËbGæ_Vé^t­KÑvÙE•÷<†Ä‚Å’Ú­ê¦$ª[6ùy“?¼cø{/ zbÅO¨+.°¡ ‡éoÜý Æè6”&ôRL¤ëY™‰wš£M½Fâ{í j³ÓMT7ŠšP¯]°7?8ÞwôÂ6@§z¯ì‰E¹ÔñyanËÿ Ð7¤^ÉÍäýï–„X¤~€9ÍR)À"Š KM8ñ'æ¢ÛÏípruJ‰SççZ½þ·Ð¨›9 €âÍÔI°ÿ— Ï^þ W‡:üž÷€”àOZuV\«<ù(– Uô˜&ÚÐ9g…—³Ä¢~¥©ÂFk@ß®‡§´±æ’ÔãÚ`R')Ö¹šÛÚ“ÅóþÖP©bdßÃííöonðü;‚ä)Kð…Ïê5ŸOpøu{t_% Ç"_¡tL£7vÔ¨ªÈê5Mâ1ózÊEºiªÅ<Üé³I„‹Ý±¯Ìëm?”eõ6€}ÜÿnêäœÔE…¦]ϸCkòÞÃ|ÕÓ˜w¶â†“y ª£+Èö’^²3  ÷­ì ½Os†€¢x?«ÆÌ K‡Üj8BÌ!B€€dˆÑ´ãÖò]²À±2 Ÿ'ÓŠèaÛÎâkÄ’ îU$/„5òn)Ë×DÏ])…’&Ç”&#$»¸ ½¥M˜-B+- úµÒ««=@·'´ß⃮ܔQ9µµoEÙ4Ì}—fçÏ,Jý†þékrùNT‚õšëJ¶a˜¸ÍþãQ©“Å2™W¡9*¥VFËP[6·¨0ðÙë ffR;‰a’×$ìVŽÚ} ÍK—zØtT’NÌ‹†VYLP”ЦQ»ñìÏý/K†¿7©”€‘™BsùK“ò‘ô $\SA¢åOìù³Ê<ï—Úb ”ï`/îoåv$!30üôáÌõ†Øà¼aeá ‘ R»,(òè6¦;Bù½M¡$lk1MÜH5á„ùÆN2}éðƒIœ5q`$• *sLÌã6\[§j¢Òf+©þÊÞ`¹C9‹¸ò:”ümÜ5`ŽXV|*~…ÑCœ]•ÓVÔ>– šèL’‹kwgâ·½0=hîÆÐ oÏ£p'‘ÄÆ¯žÈ T4š$þæ^,"*縌0…£kóÑ¡xÄî80>]ØÈsiàµ]ŽñË<ëÇ;ø¥€FëKc4¨µˆ:»˜úÙ±oVמNÑí8xŸL—nN†ÅIàLªhbÝE„šÒíÖn+÷PÃmÓ8Å}ÚQaŠpÏ”{S/Ñ@ëL˜QŽÁnŒ³€ÀŠ!ÿbb¦LÉiÞÐ)ë³Mël€,ÔA\ Š`ŽÓ›•‹i¤ó å º+z†žsù±¥YȄր °Scäÿn†×ÈæªÚâׄ'¡ ´ƒ…^°–@•DzùÞÊ*‡¿µÌO¼iOµs¹AK3§îûZÌb³¸:é Ž ”QÐàxÜž*L3J{“qí¾¿¢¦£#×s¬quœ ¬j'ÌD;Zà„K.ôe’´ÿ}ЉÍÛt¢y'ópË;̵ç#£™Â#'f͉nþ÷ÁÊýc'Eë¥/Õ:[ùϺÜ×Rïs MÈì•c¥æçÀé]žDùôq*6 p¬5ôªÀ›ÞMÕÖø"¥*$’h}j·>Š8D áZšÓ|Óþ¦&·Âª¹Ãp± \d€Ã,¤„眴upÂ:2ž2O˜%í‡÷XnÅ’óDå.F—–†â;Fw›3›JôFx±§Î1%¿,\9ºÞY4€¡¨3[)t«#~u{ ƒÐŸÖ_ ‘"³R+B¸'ûÁï µ›ÛîîY Þ`eÛoúV¿Ô[péþ…˜¥Ã•Nö«tM™F‘gEÅÑ?£Ù/Ùof5xÎ9èZÕ0&朖jMråA±îñ5ØYd³ ø‡. ^v§€H-þòcIë)J^…aì9m»99ÿ-{pE;Š2Ñýa¿:âøbáê°Â½B+Vß ¦~á>’Ù|]³›y—íúv0Ï=«8e€9¦?0v,¡{:m ápOÃB<íç‘ÈŽS³ï-ýCtE×|€{éªâû¢’o¹¹®g‡„4ÿdi&øªë&–fᩉùЏ&.\Ñïüo‘ÉNªÅu^T)ÖÛÔ.£ó+3/Ëß~Ãá)Ã~ªAôiœ(r_Æõz§/êDys° » ÝAêh'°¥µ\‹m¤Sk•óÂ̼8Š‚„·Ò¨ÓfGegö™™bÐ*‘]xº:Ÿ^aåUv o¯†MFM³_Çu/zÍ%~=Ø ¶ÛêÆ[€é˜ÈcÀP…`³sl‡,/†ÚU´‹´Ö¶n#`A )f‰¾€–=‘5XÌA.C‚¡A+€¥9˰¥ïRÒe™¹6 ÁÇîÙ4)r]żsÒÄÀÆêÍE—³$ ’Î:wìR§Ð”léUÒ”É/ äüèbdÙlBl’­:–¨ S¶ôwK3Ë2Œâá%jª§Ý¶3e¾(Ú³p@ÌÍ,«½¥)”Yay{LÄ1@PÎ7fzW˜ †@Ã^òØËãé {3äâP€bF‘àíoýùëC~ºÎ‰®­sAòk?’c$±÷,v«}>ðá¸uSnnnš~Vsdý^ëÝZ„q%¼ÈyÂå$B•­Ëµ•\FD˜õá¢ÔóÙ¥¢UÙíú‡êžúáÐJ–˜?õ<ºç&‘‚—‚+ÚS×´KÇ`ßGú3ËTåú–Jƒ…šð©MÍN`Ÿ°'[íÑùZÚÒì!ëctkO¸ÔCá* íý¡žÆéëV­f(ªC7,·w¡Ú,e¦ =Ïóxà k[«n#äµi¢š´"F1hfððÉX:89~ö§¤­áœü½»ôú†Æ¯-D»á5!£tóS4½ŽeTäóGf àŠÐÒ 6ù‘6¢ñI‚Ľ‡ÕÔÐú'1MŽíe(·ª®q^Άø˜/t “ž»¡1!5òÛÐFäT¥:ÆPÚð'ú=:—…UƒéÔK¾tt²™K«S–ÜX“æD[‡€dǘmâ²)áŽ%|ËVCÆè:á-#bãuZ¸ZŸN@ª&ë}AxªÀ´‚»7Q|ßçg äZN·Àém<á÷¹¾ÐV½&ÌŽ;ù='—iaK’¬^²kŒj…¯`µnæâ2þP¶¡&ELÕrx²Æí¦=+îqýlWê¢ÒŒ²Ñ*ÌÞM‡2èbøh[¾ÕUOû4LÍUYy~ë‡úñgë‚— Ì¡·²Áç]û ‰ ÛùNøÃÁ œ÷ðHë!è†dl½¨ÿ+0õ/#‘œût°èžÒ¥!6þXI ÎêåÿG4a)P{xX)ÏBÔ_Å»úf1âäéZù†·þ“?Z¬†ˆŒµwR!óaS6T'ˆcïòãÖ½!M«¿ÿ k9ÚP$Ê”½¢®¸•rí™?K‹% ‹ßj S·©‡‰‹Çu¸’Íþja½/<%ËtØôtQE#ůûÀøZ¯rà¨jÄŒ*×}ým„]v¥=ö-š ,rfúÐ |§Ì>õ:n–Újca É|Á”m7'šÅˆ÷°qž7ç4.­ÏsŒ"F¢@(Ã.÷Ø–mAëUì+âÂb)Ëß8Dt¾ó™ GÝ[¶¨~™¹&TPí ß++âL­÷û(1¢vøÐ+P”’À¡Tx"l  •Û ~›lN_Ö–¯öÁró:r›ª]--@ËDИ6%Ž5óµÊ3¾(ó1}b^¶ÇEN”@ñamóì2·gMáåt“Ê5Ü ší£®µºy,°/x@gÝÊ,ÎCï?—žvQÂi_Y9ÖDæ+N ñVè@Dè4Sñ?Íuóç¾É€Ñg´;¢Ý²ñšÊxñm+ź±·ëGŬ‚Äx~…@˨¿rjÐͼöÜÇMhŸåp@þz¬)†=`­TWhµ]é¾2žÅ•q_ßÁ£ÆÕYŸË®'¬3]X‰8¨Í–Å%Øž{íN_&¾V5 % ©«¾ÙˆîõAóhO"?Ü€Ç*‘ƒ™PÃßÐÖðàVŽA+;‘!+•œø¾6݇…IMá–5³—˜úÙÝÑò[E?´t“û¯´)*²²2iÈ…¤<Ý„»l=²ÄÖ³(r̸>ë÷óÊ<)µíð§¡{Mn30“:—é[LbXkTì¶Æ8áÞÁ+âÛ]›¬ÚC°#GÙZ{|'÷0¿ƒY,“•&E¬këDPWÝ\¹yñÜ©Q0œH¯`–Õ5…æˆÕx‡<­'8úº >°Òf¨‡sZî³ùdKëõoQ{Žu>ÝcàãßÙF-§„Èvki.H6–è)Ÿ’ñÔÍIÇáè??@¨üý€äŽEQŠfèƒFÍšŸ@ Ri²j9Aâ °Á ¼ÎÚ£9wÊÄ;ZH¢L"ºu×wôvO›öÛx”>¦©:AFh³­\¬èÌ|]5ˆ}±ÏC ©6,#I‚¾Ý`Å2Ѳ=^SXè®OAªg‹?–2ÉR'Ò ×e.EƒmmŸ™˜;£×A&QxœßfÒ—Oôû+Ô娷½Ež*GIÊ?tWûÅBîáp0€Ã(±ƒv¾•: ôЧ+ÎÏGZЪDÍ5ÓtËÀdÚÖeÏÇÃÇ›m“È‹ Š™&àÅj¯ë¨£çÍ©Ô+÷}!úÑ‘ùjD1po¥Ë‡ã4ØÛphZïw×­>;“ö~í‡.éj±‚­ÅÞ#ÿ%˜}]Ò” Þ ¨ g~w™90±JP×jäɤ¹ƒ …vc«¥ÔBz1ÊDñ T±õgÄ)4_hSäg$à\FÞ+ r®MmϵR»—àT(Ù˾kÍ®ºdqÿnÄ9wž^1=/¦ovàÈz~ÎH|…FQaD÷Ú/@’Ê˹×!Mž|ŠïWBÄ›óLëÍHê¾a eÿ?è Ô¾ý##ëÎPàS3Q¾oO*Ñ€pr´î½Ü/ÂKÁ0ús¸·x×.©3NëícÏMPw““›Í&Ú(m¨Þ6¸wM/Üp#“_oîâ#†ÎÐóšëñÔ6  •ð°_÷0sræJ›ú;ò¹2ƒ%o˜Õq®ÆŸu0™.WÊdTÝùeShWÙ¤œkQ:£úÛ™g¼+‘ÏŒQÁm2Îö2R׉K‚¬d#[ßZ .„اC­uᮨ’ގͳOŽóŠeÜ 27ïék”(x·’‹ ¦ ß§†¶ÜcÞø­'ôFþ¸Bäýp$Ó:P–÷OüÎq-ØßÛPZ¹«£žªrÞ6Ì…C˳^¾ˆ[÷Õ¸û ÂÙÚö†.¢2˜«íFNå‚\LÚâÊ$X>Yˆ‚)§ù2I%ÑZˆ] L]\–ùF¼¾‰J7©û¨Çp[xX 9|6vª/Ib[Ì—›Rfw-é·7ëh#ѸÃEÞoj1*'Þë`+V•›Uð¦@Ý㵇DðK,¾ñ/m°éÁãm.ÃöEËw(ç?W¸vkÎÒ7j«R å+¢Ók®¨cøß"ɇ• G×g_Fk{–֙В4U%I·DçÙÖŒÔãÛ|,Wú€g˜ù’{Á7㕌:~*µcæ0Òëb{ë¨:uCTËðd¶Ôtz¯@šã¼¹Ù“juf…PþwûO2ÓCÜn¼6¹|ß•}cP´/Ö¡-4òøLžÅù>ÓÛÐ1b>Ðø²(Î=eˆÞD–Ÿ‚`9fAA>-[®âœ™H#íQ–~1 ¶Í©ßÔmuúv»nþãö?R´>0L‡æ h¥7 Ÿ–!¼’Æå’z0žmŸ†ŽR’ 8ž“±Sx?¶@(Ç®ZV#Ÿ£ï4Ÿ" áÏ&\Ðéc’X·‚JÐÖœãú€ŽÿsNUw£ñ'¶ãº©sƵgJÑ Ã\ÿKG&Ð?û·³Æ­ ÿ{·™Ù–ÐP_¯Ýµ†Q¼ÇÒaF.!šIU“k^Úò]"&KÞ6PW…„!Óòd…Iý¦ãØ5«%ÆiÞ2«Ü2—œ±ÖVA¿ë ྒ3Í`XÏBa ÏÇôû`õ€rëÔéóÔ+ö¥W×Ù¶¾NH±ìü­UxÅÕÈùÀ)Îä±½Yû4`ç¡=”÷¥Ÿg¸¹¼y|ÅoP§ ¡bÀi n ZÀ6ŸHú\3©’+˜ü®)DÐÝÍEÊGšk=ýzÕîÎ÷uF+_®#{“€ï«ïßïÃ(µïM}nI%ê¾:´«z;ªh¼šb½ ˆžpHk?'ü&®MýÿS?þµžl›ÐeâÒéZÃfÓòW;À1mï/.ŸjÒÒpŒ´¼ŠW²;bÇ.# ‡gDÆW ›“]‰Ó¿°ªŒÏYô?æ:sÚ#Í¢$ ×Í Ÿ\h†f_à¦Ý7½ךª‡Š·õÓaV¼@%Ãÿ>¶döZZ…Œ×³¿[€Âl!ý`ñ,~Uy¹ªQOç–‚ÔXñ Á‚ñ¶ HÛ‹h#Ë$‚ièiRBV«ˆV™¬Ï%š‹Åk=~Ãå>Z -uöæ&µãÆXy¢¦1Ȫ?/³;!]9µÜ:kY·htƒ•Õк“¨M`7¹i\ºÜåõEÈ·ô ÿ4Q²ý5w´3/¦cئY®…bý¿V¬Æ°¶å?׫‡š´OÞ­(<;o5YZµ‰tƒÒ»A<ûŠŠ7¡Ù‹xâkVË÷W³Ï¬œŒNs9ZèœiÖq¶“Y{hày Ž7ó®žÕ79Î; ØQž)ŸyBÙ«Ý.cŠ«'ñC×3îÃãˆWRûq¥*ö¢ÆÊÓ\m‡.Ü+fõnõgÎ}62ïçJÛçD¹¥ö—Ž–¡ö$úåÅ 'aÞ¡I…•¶Ú›×#ƒ½øØÂ"´ –R ùA‚ìÜÊ$«íX‰†vÓù†¯ AÜÁ;¡b•9zƒ¼‚ª2PñµÂs“ñê5Œ§ƒ¢jî˜ÑbDÀOÀŸœ;»ÝìR©¹®2S)—¾Û€Ÿ¹Û Ò“=uÜHÆ ì_ÚAè†ëiÝñ eç2l|è-›•A]ÖVa®ÙHÈdq484 ¿˜¯N —%á¸ät«Ñ.>,Ü”«¦Ä_´ÁGUJ=‡1™8-+Ç—±GÖ”Ÿ}°k³g±0æ¿*¤; ]ÅÖB’þÒS¤½±­Áõ~jùé…jÆ6Pp´—Ú£±Xï»0&¥›ºÃ!™šY9ñS}²<Þ˦þžÔ½»Cs¸Í’¤žßù}ÆàP²SnIÃÎ1CÆ·ԹܺY­Ë&¤‡—ÒAM€ñä÷Ö_®êdT:®r~*3¡iQuhH4uAß{¿W¶UÍ•ìdŒîâÑ‹¶æXóW!‡âm9¯ ‹D 䵩Z^ã´R±f}}?¢¦è‚ñi8ŽšâÅIvç¼twKC%Â&eŠànªædš´øÈi¢rêïý£D$Fs¼wû”bÈͶ½Ã?˜¦’ñ{‚Ø[Ø6jo‰¤¥¯` A®Q#vaåøvqüû&þ}³_‡bøþžÊÿCµ?áØÞ?‡fÿaØïðí'øvñÐ' ­$™wÈ(qMZû±€dFWbœtQ¥DÞÆ)\ovjÀ.8뺛¯Î˘ñ'2áoÃXûFá‹.Ñ·ÖT‘ ÚÂÔ©T<£ xu>c¤C9€ïwߨ”ölU8g€nca¨ã}Ï2öèåÐ&ÅÓ¢ƒ0e#ñ[µßjÑ1ñÑMÒ=EßÁg¤ã)Æ=Ѐëø–T镃*£Lñ.ª P? ©h/sé+ŸTm…ÁFž7ž&Ý£­‹L¢¿£ªöžƒ·W-mXˆêzôkÍ*.D‘öÖìȹW¥¶æ,¨«ÂŠ2œ×ew¶×ÊšþªTΓ&ç¶õßês)`ßø¨üU¡ŠîÀÏžCy«P…˜˜Á‹Øþ„¸Îº ½¼>®'bØì.ï»Ò J`=EùYÙViÕ&ÚÎKx¡8ZY¿\xÉ NCÒhé"¾‹µ;²cMl~I>ħF‚ ²ÍîÖÀpl.­ óqü$ ÿbY6ŽoÏ9Qôz­S }u¯}=VËÔÿ%á;ƒãƒ›ÏI)¾ráüg£Hwô7ò[6ª¹ôñtÙZK&·Àl4±Š£Ó5¢«çûpK¢ú „"J/¼8{Úž ú!®æE'  ›w~ç%.ƒú0]ð³Ùõ0ˆNÙ‹—Hh <óU[£[ãå+`ú)¹q¾ç­¨™Xá¬!Óèí«ùèøµtw{“V>ö[REÅ+Fc•ãx~[e·Eï ó~6” øÏ+®¿´[¶¿ öójxlüÔhhò¶Å£þS+^zÇj>WGæsÅ·y’@Ë/cJùÏJaΊ¯‘ÌeÇW«°E9%Ü©7·)Õ Ól‰ã-©Fþ-BŽ&e'K²ìxx°¤#H?µL¿³³§#%¨¡mí«>Gx»Œ;(£Çž ãJs&ÖaÒ)˜àLhÀ̘ªO›ÄÉRw•±|ŒÀç(bM212—‚šf3=ZâæÍ•ÇKýÈÖhÀçqù5ÿ`÷)^Ƙ+­g‡],LïºÞvt²‚jÅ„:NÕ$Q3<÷@ñér;uªî4ºJº]{®ëž¤æ¹üÜÇG#¿D“‰Üo&?”äØßù*—ïC¨ˆ×͉ö·Ó"õ·{š L¸Eœ)\Ž–ÆëœCqà'¢Ìó¾ä†çfÑZ3ªÿJ|uq/$~­ÿe·¾:¥Ë¬ÑdÉì T3߸7LMæ'#–‚(ÀÝô×þ?uq5ÞTíu´óHˆ›þfjTF‰¸]Ïàz "å­uÀ_Ÿ±0Pîi¢±¿Ø³ÛËH´ôŠa Zõ÷ÒM“á\ ‡§©wŒß:‰³o–Ìž çÅ 8¦Fê‚Óe_úY…p?\ME!†m°¿ú+`®rœ¦†€˜"+A‡Tpm_â[¾MÄ CpQMìŸÌwX`n‹’\uïÀŽŠ»ËÏ#e†~‚mzšiÄÙþ¬Ôê¹#ô×VEó \œ²ÒÌ1UÍI¿¹×“j½)ýoÏó¡Ûô+M íä–üá}g,«ð¹Ï ˜:â¥E¦½Ç“ž&`÷ÛŠÓ2)žjا¿Ø3t±¿ï#.3° "U®R¡ = ‘E‚²‰{ú»`³˜ />ãr ¢LøT|rÀúÒuÄ.ïåÙV°»·¾@³(®¨«–Õ×ÚXº.Ç"W2L>z›²mž²´w¹ZÜRÆ…-毒ù]Í"ŸÃ]QL“å¢Å µàÖ{bïË3P4=(ï†âÖ>\‡/©¦ WA\”ít/Ã2‹‡ò8.\ì)Ñ­z¥}AÎÔB¹¿ Ig«PÄ:±('3/9௓škM CÙÝVåо¹ÜÞ²ƒÅ+Hf9ÊkB¼‹XPØàpU_†åz|Ný¡ e!áð5gÐÒ_"ÒÄ¿C–²SÌHæÈ‘©H¥ßy—Jm5)È ü ÷d:¼ü‘!3ýÆî·¤´M7ïÅ ëÈ£¿¼›ÖðÍ##&ttf¯Œh³9îW-ö}¯¦Rñ­©&Q‚m~Hù4MÙë·´»É^kuÄßFÈU›O„YÝ9¹béÇ-"ó¼ï±ú ñ¶;ÚàFÄQÊ;š BžlÁ€E‘o‚…Ó«¿„ }tuÐ㧨2Öý‹O®%ÂâºÂïå”Î ~Y÷Œ«¥ DîÑtKL»{æõè¤É¿ Õ]AV–l L5jK¡V©ÜªŽ]¼jE(Ôæü*{¨8)Ž6"ÀþéU‡m|`UµÏÌXì€%ç/°ºöì×¥’dtÍ·‘õ‰5J w‹A^hè^=â{7žÓͬŠÿ¹ëj)òà–¹›PöóÇóý~NìÌìs–ر)µ\m¸Cjk{DÌ`œÏJyêøLuåãgUwtá•ãѻݕrÝ5á<¯ín#Ò¿Åà¡…®¦U ìsjµ”Ç9$ ÷ «8€×,(E£š¶^ùªoxÚyÔZËq¿á~Z†ð§˜„gâ_ ™"…˜™ƒû“ÓÙ<kÌÆ6?oå|t¹+ãÓ6Ý;-X޵ͲËéj‚Ç™Ì3Güq!â0p×¾W,%º ÊÒöÝf¬ö7Šr’Ð~Ën'lÊS‚OAc¶ R§ð­¤ngv!ËYé‡ßq1”8ZôÕR)9\ØÁláxa|:nz|±UÝé ¼Ìí’ûnþæQþ3y9±”2Æ¿–i[ìÿ&Æ@f¯ëKÈù,ˆŸFŠÙZG(/_¤Rp„þ†Ì qþ…u@ŠÈûlfªÐ_Ç+½ö;-ä'$ï‹"“œ#7:f½1_všD,‰¤Ë&7Å q›&äK¨Gžu¬ ÇaUyЦ-&gîÁžð}’ã™Ncïn8±OUî¥A)æmùŸlîÒ°x¢ ©$Ã|Ù£†~®íºEÑÒùCDKiUþÄ‘LL&ïßwÁ­", ½ÙÎÔ0Æü+¢\å<& ? ³Õ©« ò#lËõ ËqŸ–a+Örf¥aF¤ˆ™-µÒö¡6ZY8ù™hý5'ݵùÊž¨G,\¼¹j²ûj²¯a<';áË•J£ÌXK!¼¦Rçº(Éø_¤@Ü6Va뢱…~þw•h–Ø ¤êœT9,äC7³¼(÷ï.Hêu†U é=X>×õÀ22LÃÌGJ«m0À)ù¶Y«J´…¯…Ï Öd㛤ßi.TŃÃåðÌšÜ ûþ‘e¢·|w˜|XG¤*‘Ëûqàýª _“1ó1ª.Ó š(g+ƒi'9* âßJÌêA‘×ñ1ÒeåÕ0WáýV3zwh‡©YMÙcN.k{q‰ 4žZ¡Ú…§Å>’Q£…qåú(Žõ¹ä8ÔOwíëìb'ðþÖQõÐù)ò”D,ò³M$Ä×hœÆEŒGÒmç ¨º~+’æä°E™hø¬P5/x¨?Í"dÞ 9ùá, «Ø;ŸÿN#€iã#çwÍÈ-±ž·?¥/Q„Ï9<ù'øÎmâôCÃ{p.,ì)º#ÞY—a;˜~Aô¦Ì¥ ½«Q@FCßÅÃq†KKœçHæ{œ±Š©VÏí‚…7‡®Õw€ÚQ÷“BÄ!¾³y¶ RîlvùÁg}!¸ã`#·’Íí‡,~.›Ý˽4†Û|£Ày‚íÌÎ=)ôaRÆj‚1.ûºSû.0ònk$Â_£‡Êô :É^&ýùø¡KÌtù*!Sµ_T^ª8áz]èú4Â0Ò:îÏnk×ôr2àçŽ#žº6ì0–A‡?{v³«$*EIAî[ä”߯&=¤§öw qPµÿ^È>Pà Žgfœt2vœÉh’È3 ŸêY³œ7?¿/&iàsÏŒ Ø!.}Zz:ÜÂ2+l¯yuô Ó|J½[å ð#Yk'ç#»î}5  (ô/óoÔ»¨#os8fV"o\‰m^A'°y6PÏe28`~­?ðl–mEïx±ˆªÖk"2Jÿ]Dù¼`D-}È—é;ósÉ”tpg ¦ÅõÊ&Gç>ÉS)¸Œ,n˜Ý_zžŠg£xˆÏÅünûD=áû¬i’’_%ø¹ö ý•èÙ©ŒÂ>˜à+yZ¿!·-l>=’Š:ðyp®,’ éO¡Úä9žõ^gõá›è¯Æñiß é1k”4Pd!éÝjïÂ44­kY]þÛ€W‘¯Br¤®3ªêøê ¿‹ŠŽ?àg¯Q¤ãmnù¤}È—‘DÝÉeN“îÝ«Õa×Àãíd™~ýbq‘Q6ñ5·þ´´è˜µµ@|Ó‰ú/~=Ú´T›Á³K·yúï™,¹ÙèíÀð…bA +0a’÷`ÐQtbçs·=WâV³7ó\#W©÷1-} Â5rãqŠ©ÃØpÀø*õmÆmtBýLrnçðË`ϼ|;‘ø6Äjë°„ÝòÜ÷NÊÐ ™¬+ÜG²ÀLB¾4/ :¥ ï‚$Ãä% F„¬ÿ'³À¤'&׺­Áb A9‰áÒrO xTuD/u¹áŸŸ3 †¶ø&Û5øV!ó‹øfO‰T>Y~ñ|PxÐ([½zê³ù¤}y&µa Ö*Ùs#€B‰ÈbúŸÇ­%Ä'_}G'y ¢U°¥ñ`ÑÌ•øŒü${Ó3ÅuX”miåIÖ`¾@ï]áO.БV‡ŒIebFÅw·I>w•ˆï­½'Ô|ò&BÌBµTÁ7ØTĶ:tmÒq°°'dN,U jðGgv2^ Ñìsš˜ëT•{6 ¤o…k„SgØß…KƈìgRõìzh{ýQŽˆÓ¼[â[ÃO¦<_ 0™ÏšË˜+¬Ä=ºÀetóJ_ʵËâU½®%È ~âÇ$'×÷'GŠÜ° W›“ŽðC,§Cw€é³–¹T§)Òf“Ù %Á8'ñ€HŒ µåîñèèMb‰Ò¿µÈ¦å°9Í¢ÿL¦®dˆl=î¡öO­¯+O­% —’±·\Éÿ ö@ö`R=g?ÃD „×’)¤·UEÁ§Ìÿ(ª—½opf”|¬DFccϨWæ{÷sÆdØ\•¢ÇyAXþ&0qsÁ™¡ú[{©Í[w’K[×ݶ½ÌŒô[ÃVÕV1hD™IQºæ‰ f* Ì l¤Ñû­áaoØZ®7pÔý5¡t6¤›Øœçþ–)ù´'W“ÿ` ¶ ”f5Ä(/Ÿ7øãrtQO•hJùJepŠà[b·¬¹¾„<+É¢)wx\ 4‘«™lsV "»I)gh%A.û²ó¾fÁ…“}·î1A›âLÚmŠ„‘ˆ± xûÜ?Ò³õ+yª‡Çc£L.twJS_=·ÆF¨Q¤ ï2§Wnn˜ü‚cÒæd¿I Í'#‰Ü—è jŠ•¼+*}]mœ`™«Ý«…¿ØÄ€D811ÆÊ"rÞ{¤}üÔ@þ äv[• 6¯—X…äú†½í¥çå>pXÜ3“\üç»Ê§Soö½Å«lQYiJ º^×Ï’•jÂ~âæm+òNÆÊžÖ­£ .×Îé¹ÇÌ$‘¶¾æ‰V" ´TâÍ{4˜>8š/Bæ­ÉÐ9K–×’œD‰¹%;­„•%a Ì ((eR…i¶àº*‰²TûŸh´^MôÆ Á2‹{W‚U@Ö£Ÿ&ù©»²wî¥5ÚM 9&` !Ÿ)ž‚U‹0«b]ì©~aà~Q «CAuV±dmcx ½¼^:f/Òvp¨¯®ŒÉÎ7ÇF‹^YéfR/›À©W·=ÑY6ø”%HòëÇ<ã±—™µï7è[’Ub½NÊfÀ®Г"C­ãëÔ¿CÖ$a?f~}3#×ÐÓÕVbõ»ôžôÐÌtás» SµÓtbF± ÿ/‚,t–ÿ7Á–¢ÛŸãD 3 µRÉØ7PÀÎÔ¤\y­ÃãùÌ5ä.& }7™gÿXÏ›ý«ƒ½ F®eÛÏA­ Ëìy“Ĭg~ ‰ 1t<’ÈoµæíÉp~¿>l2(c)v]P©"×›à²\馱­¿øàÚä…Àwz3Éè#j%ž«¾ÑUˆÿ ƒ4{àꉉýpϸFb¶â;ÖYpgiȱ#ÅÔO âFØß,š“oÀìn>AŠ‘Ú8JTªÛ ô‹ïr{%PŠ™°áP§”Ô^{¾óÐ76Nyš1ÞûvÃÈ=3Äs¼DßÑOE&ÕÁ‡Nm›²½ƒ‘Ä·°R>cT9œJ_!Ÿ¿Â‘<å†Ú˜÷j‹¢ÁZ?Š8ˆ)܇ÏÔvHqõÔ3¿@kRK!õPÏ4†#«&– O&ÄVâèÚÇ›X‹‚úèëqÎx£vÊA fËÅ'ù*:ž`3Ì.3"é=ÛÊÿHß?Í d¯6¶íbÞ[²ó}dšÎõÇÊòEêjÖc „ÃÂà¨2€ªo±ÆW¸¬$žÎ¼»˜tÔ⟳ġ´ý <â¯ob ûc8EétP5é!*÷Qª ^o}•85–Æm¡Ÿ>©>;ÅøùKNgÓ†~ú3™fágſث« àux·-œ7êIp¬Rø+z”Ø@0ÃᕳaBî³S—/Uü2xvÅ¢aÍØõpÒ¸h_>©TÅ$8ÃôXilPKÕ9óhfZ\æ2œ`9si¨fP‚ˆ<äãWß\*%=²—óɤŠP±#IU©-£ýПŠó¶ûÝ·ƒÍÊxµÅÝ)Ý‚«°ä‹»¸f oÊ›OÆY°ÔÈl¿†;åJRêŒ1Aë˜AÖü£.À²Ò ¿=«¹²¬Ä ·5“¼9&6UöVž îh}«z¨OXV*Ì=sѵ/3”“sôtmíE½!cÙ‘5,<§{¿rDI/÷÷ö [-¤–GFºÅº›Ïy–Xn#)Òãn\kóÅ K&lAÍ)ˆ¬™Øj¡˜™›3îgÿ ?¨Ü˜ÏkâíÐæä­™Ú¼UÊ|…[¬ºQ“Fà;ްiÂÈuÿx©56+ÄæK|=·m»_Zëò½áMåâ±ýwžG=—! á tx §É·É+ ­¸Ä—>Þæ%4XH[ ®[»•Z•âŽ;°n O Bm&ƶŒë“*uH]¶XÉh@Õè¸ßS6ÏŒ^· ’„ä¡O…òmÁb¿¾JLxW‡14ì3ö8'®D=ZÙëÕh0*3õN¡{nšbh4V:6ÕK|7ܶ#Žø××b]¾ImøWNÜ„ûЮ©#0£Þv¥×ênF÷›}²âOUo¼½Šü«çtÕV²Xã#Nµ ìú.ãÅ-2ûÖÑÛé¾ÓÏÕ 5d¼ã0•ygçÊ/âçÀ£ å­U+ªäühŸXáU]Œ`Hb\ÙHAvÀkÑ´äð…$‰Ÿ¯Ùª‰èË‘„ê'– LöNˈÓçóM3F‰)'ðdtÁÇûßo…e§:/æÎr`*LƇ ÁáÚT¡å1Dåî¯8߯}eWÆ-p—D‹$9r{.S™!²x1RM“Øm•e‡Þ Åu“ϳ3`ù„ؤ1ñ6hð³p 7÷+°AšhŸÌ›xü—õÅÎ!Ù'2×îÕ-f2ÝoQ[N«{-s¾£Ö`Ÿ”ôSê¾a–U ]¡á¶Ë‡Ü^¬< Ҳ蕣ì=aáûvïöííÞÛ³~Ý·ý»SûwVú¶;Û¶_·gnæþÝ…ñÔD FÓC_‘*ÅÌ/xº9è…å™k3˜òGÊõœÐFX/ ¼UUéÒ_–ý%œ­ÞŠN_}&Þ:š:ÑgañÂ~2¾Å‡ uhí`vÒb@]VíÀÛå )ß‘©Ä]*BG[¾Ô–WƒhÒ+±[ˆŽ´€p:6¸P²µ‰'™ÛL2Ò…sø|Ÿm‹ÊV•Qr‘iÕšœHr„Úìœ}S§§ÜLĈÚä+m*Ô¥[E è¥]•œÉ›–=7à?•…,fÓÖ—t/Ä·=¥WQ¸žÙíÅ™#¯’I±ÓJÐä†k*]ÀJk[øô¢p^¡±¦:H2Ý¥ñ†é,†“ôÇNï¸Uk5:¡‰3T&Ú:–¬ 2Ûî¼äVXƒšÃj¦Ç!Ò%´¦$ì¢ȸõ6kHS³Ð!Xl­».ƒ¥•ÁNÞ© Þu}® W òûôÀÕX­ö2¾Žº+ Ôq~œ ¶ÔâØ™<ÒÕW1|mh4…TŸé\P¹°zV‡)Ìn’cùžZ¡övF7Ù¶!R‡Æ¡œ=±¼?¶ÐÙîÔIƹ®GËDônß@@)¸œíoÎÐu2ÝI@µÙÍ >¼A Í ª˜™G’8"8©àØX¸Ôyœ“âLË^¦€õúˆÍ<5´­fr¼š¾º;Db#" ò¨•‘5ÞÅi¶Õ®UÑ`8ä¼û9Ká3- qÙuˆóte¬ó"a² B…–M‹SëÝ@sW©o[¤CžûqcéÃúdïrc½d²*ó².õâÜ¥yŠÌlÙÖV·@y/wð|à#3ìÄWBÂnÍX=8¯€´,d¬(¯ãzùù<_Zrh€Å„KBóÍZšª§¼‚ ý™ì‘W¸Ë£'óºùí-¦&%æôw[4R ¹‚y™Ñ#°YÙê8ºM'›-G›‰ø­ xs¤O„ií?«õõÝÕ>ÛK5ÿ üö-˜§À¦v³h¶†Ä‚ÊÆ ¦‚Ñ ²`JµÇ(Yç´à™{PZ¦ëð`V¯ñÝ  Ëk”¢³Aœ^:©@v²–]á£Wdš”̸^ð7†ˆ~¨äwïTèÂ=˜Wo¶M®éºnî™DÍ4Ða8ÀœÌØ·ä6sÌN ›rÓp”\–Í_&3`Âé9]é@èÚ>¾Ãah·ÌNDOSv0=Æ ýQm¨î©ø†V‚Ás8=* 5>Ö ½ÆµùkAriáåϺEU„`¹ì}zAqªX#›I‘›h CSÔ)‡îI-ó‰°ï%›N V³$–? k”c”šÝGé0êÚ*Œ¼ŽÎøõZ+÷W¤Y²!<•Ïë›*á{8b¦AUϱžÜ>@ÌZH ~¤ìí*àÿ€Éý8ëXÊ ´yþNOe¿ùéÈÿ'SiJ:á¹+Dùô {æ1Š×Uó ñyº}‹Š´7q”¢¯bN(G1oZ±uFîzq,›sþ`r= ¶0Ç)Fs‹\0?h7 ‘©ÐSY5‰JfˆnÎÖ޼侕¡ˆ¶NJ¿™aÅ艾8 $oÍÿ(šÝLf±O„ùãö9*’GF9®âT`Bd)Å¢™nÑZv…àV¦·F–ï©=(Q‘ùåúLBµ»U<™Ó¡aaŸòÊОv…£Mʇ¥»‹†Asif†LæpS “ ‹Zä`“9$F­µ8ý(jòpZ¼>eþ¸ÞhØŒsF=J®G7#¹Ç^ÀùƒWÍxX7#+šñÓy£Ú[œßů ;LŸPúŒ÷ëùéúküvûçJEjV:išÃªì²Ã†k°ÖÅîñÜù{ð$?s¢¥ý "çÉõ{O;67—Ѐv5àæÁw½éhí‘“y‘7TlJ“ä0ëïôJ}D=Žˆ·–+v¾ÒŽT¡±8‘Ý~:,s€Ú“97¦7—g 2ÝÓ½Êæk ³aÚ˵EøU—ýÆÖé`áL½Î/ë•´ç½Õâ‚5ßÛ’ç‚,ØuŸðá¿®'Î5{GPw,ÃaÞù1ÔwôÓhÇ*Z}ä2úVQõ6š:tlœ‡ÉA‘†“Iu±è¤^÷ÎÕ fçüìôryDq‹>mFGV'þ·Tø,Ý9!ÍRRbÈÓòAÒ×Ki˯WSáDòvMº×¿ü]uü[7)-ºÚò¥Ëq%¸|,Öß Ý#w‡ŠÅuÁ‡zÜ¿S츦·¥a+nå‰y&ZX~‹l¬Í<)€D¶ ið¸Ê)t«œÆZew¯6äùHeô¯ ÈmGKMB‹%ÄZ$´KÄl m‘X‹‚hûŒeµ¼þ©o™²ÛgÓ6ŽV~"ÃeÆtú_mt‰8;@–=æË„ê èNò…xh¥Š©Õq6™éáR˜ „CþÞ\)ˆel‡+° ]~Ö<¬ˆÂ¥<»‘uÄÁËFH¢/ٔª% xf9  æœS²cÊnœ4OÍOß)°ó]Ö¼2¤ekõÓÒ¦7ÝéˆÝû¿ÚßϦ%-é´Ø›ýÉݬ¯?º±Ÿ)%…–Yˆ\>$›¥RÁÈBHþÒ›¬ÚÃÐjP:·ò¤/÷$/OÅø§k_¨Uwqƒk(Ð?‡ •ÔŒbhÇ×&ÿ èäÎÑ›•¼ ô0‡ÇÏIUà‡Õ¬" .°²`"“h+þ…ÝA$Cƒò Då"LÌ9+˜»j7f€™dZ£‹þ[p\6½òlêçƒû*Ï$ГéP‘>|Âì°åIúäŠÙH}~œ”Oޝ—.ERL²÷ØòФmèÓyÅüñ¦µî0ÒÚ%{V&¦[ÜñÎêñMa’^©0)Q»úÃ.áÐEìÏêŽÄ©Þ‘ðB» HÌṈ"ÞÌU´Y˜÷dƒ9ÉÒy^»ÃxÛP‘ûѬEŽâ¢`²tüÕö܉Ì*Ó…ü£Xõ!"D¨‡ãB§Œ¡ÆZªÁOvÒ9˜çJ„SÆ£¬´ßtèP÷gQet˜Ð(þå–]q¸e\juÒà°ÏO„åǘ[8$Ïfuìÿ ±ÝeµGé‘ZC,ßÑ;6ñru fòýî&µëpUòœIJöÞLkŸ<»øqÌñjò»’˜éT›pyŸÊYz©AùmVcvÒë ˜î཭€CŽù~o;yÇq0Îz~z¬}Ʊ›©.Fêëþ]Z­¼¼0J$þÇ…Ù¼|tš“—.áŠ1=&ˆ"׬ïüì,äŠìÝj¨ÅÅ  Á`=†¢3÷6wÁ®‚úVæÃ®GÔVùuƒ X)pzã^¬Ê RÌUÖ;ÉyÞÜ„fDu—!“ñU×í¿ùã}ï*@œ¶ËøC ýÉöÓu…ã~3éŒ&ãõÎ]ɨš U]ŒáKùéäe¬ÄC)Dô›+í½§S}Û(•wô?§@ý Ót/ìâ 5¡Ü«ézi?…ªð¢bDH`WbÄ5wn‹‡]%wª% Tlô(!¼kÀ,ŠÃ/"69‘ëçlev?`^®…î$Fô8îUõZR3‚ÃUnnd-Üjô016¨-Ÿ„4çÛ¨°I„úåp8„ì¨ú´žŒŠáyc©¦F™<¸:=xœB,&-ìæi#hö¸v{óv©!òIv#äû#•¬ˆÃ:»øQ›ú™ûØ2wB±¡ 0%ö% !ö>8”(˘†—î{Ò¬<¡ü¤ù1€"ßGvú⽦‘ÆIK™»F¸3Ô‡ÈÛÜ¿äcùðê®Ì‘`«ñµ‚u\\·ý“ƒ$.àÕ2„õÈ9„N·Wì¥yüx\–c0 ôcL¾ZŸú8@ÞõN<Ò¡¡±¡3éLñ¬s`8¶2x;ÉÀ—Ÿ··lÅ7öq;0«C_ѤëưÉb ûæX@í„¢€Hìyck­.Äê¦-†ê«™…ôDµNÝrr˜öðv¡„,–ƒâ×(í驸c—®O>vÝò¤UýÁ’å%¨.˜BC¼ u… Mª2ÒÓ­¾9 uÜ»ßfãmŒs^¹1(ðfLoØ^’_¥ev(ìñ§ùûm ʽºÅT®V@Dþ{²Å¡??bÝ’•YÃKÐ@A»X*þ5¢`o3‚=kMl³ÎôN<‚üÒê$ \Ò†»H[-ð˜VDüÝ™· $CÆ#ó3k-í¨îO>ÎhÒø‹±1¢&ÎÅ!¶PæÙ{|{û&^ÑŸ éy þ|Ö€Åõ á9¶1ß1#½ëËÔ<ûYß™IŸ 9"«ðAjÓ/ƒûÏ ÓŽ¯bƒˆuî-¼nÈåçAîw ‰+aâËÅÑÎw4]ÝÀ}EîÏlû>%€´gû­&›f§E•?^-ÉìßN?ìË>é]õtù÷f"cÛ#uóÎõk°8Ž•ÝU†‘ê°ÇzDûBð®S’.D󦲠iœ©ínm LJƒ»-Ñ\ú“§x¢Ì‹LPÙ.©,nT¥ÿ4ìŸÁ%¢GKÌJÓØá©|²¿S}ºÏøÚŽ[†Õ-Œu{ºj³n²ðdñ‚5«pÁˆÈtÑÒš¿1Î&m^B@Ñ3*Kß7NåcP;ŒäÖÂï–ÅVF@Vß)‹lv´°à¥mÔúTÊšç‰Ó}žZùˆD’Š Ñöä+ë^Sö7 A¶p–ücçR}ÿ ì`“Øè5ÊÍc|:üH¾—Fi,E<¢ÓËûHƒ;µHo©X±Ê­P#DÙ2è­ÌhíU–FúS­oí#/¬ S²V$º(Ùå°·Ó\áÀ¶ý¸o+8eE -S½R†CÀc‡%墮רƒ¸P½±d ÌQ±ÐØÑ®ûN¦Ú‚¯k¶¥BD“1`];Ý»@zΤ˜í‡YUµ°ôUN¾B%ünÛ—òö2%…¿Ä—-þd¼âW5ÕZ´tï帓ýKAäM¥ÉÒÌO7þ×%AÙÁ̘¿UEº·£í!=@q€çȉ Ëuj´·à žÒ¿UûŽ´oÂ’x”ÙËoâF‘{ùeÞ©ö h½¡¢Ö ©MÉÃBìÕÆ©U·â XçÎ_ïÿºXý%=©<(ޞЅ,|¦‘ð©–ÈF[v/d5ü[ îúþw¤·î¿ô]†'µÝ~«§Äs¿5Çþ­²}SŽ2ð«jõJÈÒžS¶W1بöçž/ÌWÜÞ¶—`©áHî`ãlO ¢^óéV¨®%ÀÝá‚S ‘X½ŸYÔÓZ}\]û»ô`D* b›l–Z”³¶ý(Ð Pºk¢#T|ƒ{rz^Û¾O@=‘__êfüèþÇöªŠŽ1@¼‘ Åo@9Öƒ¹©¬¾¾Ï©1};:/Z÷£ûŸÔwÙsϸæFʇWÿHðp~«:™Ÿ[ð8 $­´¶ éè¿«—ÒM³×ÔôR¬3*¿ûÀØ%vH=ªGªLÞQ¬g¬¿¡}”h#˜çâçØÏ-¦;)¹x£s°'DãR8Ö^Îæ°.{–0VsuÏf̈FKÛß+¤™7’¢‹DæAqÇM;³Ê)¥#q¦ÖW¤)¡f^‡žØ•4Ëk»g¼]ˆ2F͘¯‹L|}žðc;æóÙfEÒœ¼!Ýls¦Ï3gV0‚‰ôW‡V1Ǭ¶ád‘’´Å^gÒ£…º³D{{œIÁìÏm¾š”©IðÄcýÍõÌŠPŸŽ˜ˆsGoÝ3j|ÿ(hÈ|)€ágÚÙç§ðDèt¶$¦î†Î ЊY L€¯(^%i,Bñò¹Dgÿ9Xk1]‡1a_Z½TåßÇ‹R6lùQ+D\ÔŸÕì‡3´ß>Lš~ž~Vç±ØÐCÀt‘6Y-Õ©ˆc¸kªqþFȃtrümRß %ùF§Í;Ù*±ÀBw;âÅôT™ˆEuáÏ(ßµkÿøM—[Cî…Á=¤s•ÿ^ÏnÍ£·_iõÐÚ‘Nm¶<7†–÷s] ôš†v]êf-Md—fgaÊ8 ð.³´¿Ân¿j¥=ËÖÄÌæ`ÚÏxá„OÛ·¿´_öîtOÛ¹´WöíÖòý»q}B¿·köäUÉñ}±‡ É#Š,n¡¢p¡A’6Ãhj¬ZX¬(㬘C|Ÿ±ó»}-)³Yƒ.ËFª¢u ±¶¦´z´õÛÏó?×\Å!7šfi`—Û)±~×9Ó·£èfò¡#ñP¸ÞªÈ¡ØÜöyc óîz 0Å€±Mß—§«dÌϧH$òá]kjÖ6⻫š¶lG¶D"Ó†­; $Þä罃/ÜÅUð[´º€ÜÀYóo †?勤0>Í%ü]f_ê2ft‹Â3똪dŠ®»¥þቑ1¼ÆûAǼýfh³ë=bØnHÜ"Õ»Q¡Ž‚ð÷U͸‰Wæ#ÝB:'aW–@ FxaŽ“Õ|ûÃÑå ¸¨wÎâ.„ä•4þŸsƒ—³öÀN‰YRÒ ¥c¹¤ÍlÁP‡Sq{aú9ŒŒþù§‚œ”™2ÕÛ¢KÈMÔðž|ÕBö3+!ê;Ä îR'Ya›xŠÞ¦Íb8«¹äoï1cX÷¬†æ  ‡ÕoÕ×5Mí<­7-—Mk|{}Œ7Î|ƆÇ7%!ˆ sýüC~þ>)îEx Mšõ¼Dd냟¹~¸nɉ趋(ËØ'-{lû E¬P3÷Êl¤{ïÿ|T—\-ÏUÊú0‹à}Žî²Ë¸¹DÄÊUcôΖ¥xÀÌe0äõÛ‡‡8ôBq£§¯AôÔ¶WÐdÛxÆåF0ëíÍ®üáœo©fœÒÈ+.Ä…ÖñŸúw@pöjoAõò€œe˽ÑÎ"Çç|–Š¿HÌy<ˆo!rY*ŒËz87ªí¡Ô˜÷*éázʬ°FKªë©%9Ã÷™®c/‹®³Nwô¢ö﫽øˆ}ä! ðÙÀäug&ÿ_àògȾnwÇ!ަ!}/*"7_$ýZY“G¦ L G†š›ÇV©´-ƒšÎÅ\B« Ø== DfÅt©vöA7¨R‡½5&HƒMÆìjÒ6'n/T?•uP°ü5H$½mÖ¥òÿm6†_ŽzÌknµ¶Öa Y‚dX2‘ã÷YN‹8°\·)(Ñ÷çöôSž Ši(ø¿‘½“¸½B^Dùä.ÈÖº4Ç5¾ØÝdŸS 8òó`(>˜j®Ó„¸[ Ü›³À+Ãð–e ù(Ý{ÈHK7³C½{}Ùa’Í=!ÆÔ‹†¨…ÉÂéÖª ¿e†öM@üLvÃ"^u…,­¸Ä 'ö䨊ð« ÀèxtK%@kaoÍ£çÁõ½X±€¢^V¥wM¯õy·X‘™ÁªT§âöjÉÙ–š¾Cb†ÅçûY=Jv©×Ú}ÆöMZŠŠì±½r[)û_*Øx¨sÕÄ ¢„Þ)Õ猢}8|çÏ(G@+W„alÏpk*ƯsÜDš”ˆ0“ò±'…Qö/h b5Z™ì¥æ~Såf\ªIØI¶ž "JÒBûÑÄâ|grÁÄãú¹ãl'ðþä®±±Ø*«.µO+ ýmª;¥B«xN3@õÍà\8äx° ‡OÀ<ŠáléÛ9* ÿ<çû8ÁiÃóa+ 6Q­ù$v(^–lƒi²*j¶üÎ=NQI^î <†°xWžÿpÜ Þxæíbï1’M£ÆÐýÔ`KÇ¥¬‰ø{æ;£§@‚hëBCÚznYûÆÞÖdù9Šž·Ì?\B|BqcÌ&!oP|ß‘€ „_+ãt{-)qP8)ÞmòÙÚÞPáÚÛ“~¿í+³*Ícsâ¯@ìÐ(š.ñZ)i ÇÎX‚:w¸¢fþ¬|¨ÜhdÞ¬²AwB^² ÌqjÎ݉Ç ™¸Åi†XŸZÜJüNé£ïÔÊk®+©Ê[Ý—¦Øžs ±5RÏùÎÂY.FõXÚ Ÿ—†3˜ï¦Ð…—%Û]Ñý_³-çq½?b :&r'á°JsjƒŠ'Ȥ bd”Š!±RŠk¢ü`˜²¹^$Lvö¤è±Æô/̰0„•ÍáËç8÷̬b Úõœ?‰Ó ¾,OØæ[+GZ¥ÊÔmÅÏÌÓ<ϯ¾ã7W«$|ÐM¸oº¤í®¶îŒ‰ØXV˹¶$h}Ô€¦bÓÅNPSê%1Mëö£§¨¨ü=ÿ ¿º¥ŠNpå0ÛÀá(àE½ú¬D–󟨚»>|'ó}ß;•Í:)2˜ÿL'b%SÊ&_ÿfyAû×[ò,©¬Ð–2UtÑr̤Au\eŠ;çÃü¹Üp’?Gó©À/#Ža^úH c•JtD{ÖÅÊ\Êþ׉·-¶*@· B° /–“S¶˜ÌbAÏ™ºFÊõ­ÈŸ×jùÁÿQÁ6  {ÎA¡ûì†]X`.4©b "™†z«¼I ñ`íÐwù…V{£í‚@v¦Õ ’ гªóÄþߞÔƒ½Àº÷ñ[Uâ$õ‹‚`i‘ÃHãQûÛ;n.c5¼FÌà +#Ÿœ{ì»j„x,b½sOã¶–ª5,3xØ/\ΞB²imûµ¯kq6YPZn-yEÑÙHîòÑ ³K§hJfyãùÅÑeËBèVÀv18+½´Šqõ”R`©ÑMÕÒ Ónìÿ|ÂÄ7úõ€¢'|™À–íO€XÖÖ){pZI3bzñ{:³W,p= ¯sè™Y´„¯v§1ñ\ò³ '™dA/¥Ô˜Àf÷')Ëj‹bOœJ±²z~¯à\û÷` ãÖMo1»Æ5HQe?äH,atâHìtöQuõuÊÑH‰À«YyS÷L;7:¤#k­#:Ïàîó=š$nõ­¡úpì˜ ̈/Ý–ÔÉa¨½’øðÁž²E¹ÿJÏHìm™õ.ÉÜe¡j¹„q‡)Ž=î…Ç9ŽÝÜ^NnÎé ,ÄÖ#»O«¨°âµ¢‡"¿)þg+ñœ˜Ç%02Ä`GΠܢÈñz0Û=à#߅ʧìÃP`ƒŠ¿‰/I\2$†ÐÕ§·ÇuJ®”v}Cü½«1!߆óôwl‹êXØUv×X4ñ¯ë‘õ˜±F•À§ý_» Îa¸4ô÷–XÐLKÍêj'¡Ç$jd·kC»¥pn Zc–C`!1[ÀX¤{îrk~ ²¶èT2@þËT•ß¡‹1¿z’Ê•šEƒHÄÅmÞß Mà¬VW4Û¶óóÙHS¾!ƒÊ¹À†<‰×¹\™Úh@<ÄðºŽAKåò˜/am{PÔÀ™ÑªV8YƒëuzÉqµ«þòˆÐÐWð5à¼ÿf‹›=kÜF•ø8ÊÑ–.=¨É[`ú‰ˆbÍVI''õ3OíîiÕ)eZ°º ¡ [6¢xçÔ/ݹ ë¢Ùºq0¼”s(=ÿOoI ³~ ßäuøXŒäÏ[9 ÛHiì6Ÿ°‡:òÀ5-ö5cDÛUÚ† eKP‰Žÿ@?t~ÃñÕܘނ2¹•žC6@_d9ÏŒñ $äwQ·.åøowóè"ü:¿ ýÇðߟáÐgðßÿpßœ áþíü7¿øoñÊ#”ŸÐ[-wÍ“_Ö´Ì­ƒÿDaÿD<£Zã@nñ<ÞbÓb°lcq`Ýô~1Ä^Äno‡ H@Ë9+ºI]ëÖÃO¢7§ñ+ìh×Z~qAì"Ù)Š€ˆPŒ…ÿk83¾Aðl*—5Ð{ ¤[ËäŠóÒWõ4–¯¡CŽy×> 0Èf-Ù\jèùÕq”JùÊVhJ9Mjꘜöæ¹4(öÒ°´…ÌœSïôÿB *`ŽÓŠ­»Œâ|Rã£Ï·I¢ý»NÐ8ðÍ,_b±>ï"³#SÞŠÿ‚þoŸIÛ*°»º¨Çóc{2ü,É? GœYlx6ÊWë0}ƒ®nÐ[ãÙäÈß½~PCˆî¢VóûØ}ã#µzfô®*[àf¶bâÁ¯’{vJKR·ÀåhxÜØAç6‘ ®Pñ_Â(^ƒ³…@Õ YPy„«¸ÿ[ÿ/±!ˆø^dXó±ŒA¬:¹T±Uü×_ïÔ´ñ ©h"„·Ø'¦§‰Z”o“U­ûvÄ{ü*ú{û0ퟜÎUú€z…âÇ ¨žgæ/{ p‹0už/ {!úR\«ÐóÀ×%‰CŠ:‡¾ô3}r–Vgk- o#žÒެùXà 3t+ïîrû Óä_t|nøN²ø{Lm}OüJ!H)Ã\MªzoFmâG¬ÉØd­vpFa€¿I@-e a!PÑ@Þ5þÙ®g˜ÀRªà¸ÞPé"®.;m fžz«³sðµÏ|º)W/Ôó‰=fíTÆ“•Л¦Á:5ðõeGn©¿ÆnXì„q˃ì/Mt’«UœÒø -ón‚‰Ê›ã\½+¢c² ?Íkm‡ 5Á,Ò[çÝþq†{y Ù/jBV E¬TûG;_Äy”¢Å€B Ê=Pn|š`lIŠÛGóÚÛ #¸Ÿ®´½|Ëö‘áC§}r‚/Z‚v\°ùA %©1êD{ èGùë7 r¦·L›ÐãK]ÖÍ7ŸŒ¾¡Ú @|E²F•}•zÆ9o>ó§ ô¡ö1ßÕv¶}½adé_1kb¥`â,VöM¨g50H¯:R?G«{ÔÃ+çêkƒM×u,ãÁ™òï0‡‚Á&¦éì‚8ë¤ß†ÎÎ ÑfA?ãò/¥uOò›Ø2}(ñp÷ÖH!BJYêòT©‘"º-ÎÙÎ,É ™‰P垿芻‘¦.òÈy—£§½e`‡¶h©rÞ{%!™®u›Ñ« SQÔ38eiLŽÒµ“·1ÎïLÒîÿW4´"©ÝÇ\çpvN–¢×ñêwù.ÐcVt ¾ëIaeôP`+ýÅÄ»C5—÷®‘UcRœò,E¨;Ù{äz<¶HÇ›G%f-aVOÂâúu'ºâ”£!–r-]Ýà& ËÑçù‡E>yÕxigù‚ð'¬usáØ«kSît«O]×ñα-dôC¯Ü»¹n| ¹6[ORFT[; ¬Q†ž,¶ÊH˜PŸ›|N1êâQb­õ7o ilÆŸQµ÷¶!ÚõìG×Î|Cñm DW_$tbšE§÷LN“C_$4k éõÇEÇöø4LÄ P¶gìwoIñÎŒÆýb %ÇÏ.܄ޒ¸ÕYˆsä²yê¿?»Àˆë•èg sýäM¹uP«À·~€íÂÒK¾ö ~Þ€¦jÝ.Ö“í#EpI:Nx{uêt/¿>Å&¬‚²QòA[Íé´ê -÷ä†Åó¼ciV:{(L׌ûyJá Êè°l³›?×) »ãYƒ©gDµÂ!лU¦¶?»_‹ ¸Ñ¹ðLÞøÅ¸Ik¨aAxTF¼“»†Ïk\·Tbó50]G.²fÞ¿·ïìœò‹¸nWU6"¯7Qyë´VÞ-å._ëá¦:$¡'t ¢ç é.j‰_ûÉJŨXz<²wŒ˜ë%Èå"Þ¸\zÇŽsmne¡Üž[¯ŽoZ¤&´ŽÛÈÊüP+•Û›FQC‡îˆ¾%çÝ|“L›4Hêð‰þ ;)Í·……öžíÉ{7Xà3u#ü°8WÍe3¼¦ôÖ­¼…¾/fL1@ͯꀚ¶ïZÁôùHÛë!úR*ç Š…2F±$i{ „øIUB¿ˆqh­ñÎ6æŽ% ]Þ»î÷D3˜jÜÙ]wÂ…º³{2¨ê ß´6öÌÀ½%1di(ÁñLú:ÿm6ó—ÄBƒªæðŸ“PI¡óÕt®bÙž· Éšü£ÊºÑuV5=Å:d ‡“T.O Ë¢ŸÎyªmñ§â¨×sÞ)È}¥Î ß/÷T¾ÄÃÌàF‡9ÉIð{“SÏÑ:¼—Š£¤ËéÉ{6S5º(Ѐ½›B;u:>?‘“é¢T‡Z‡H{=ý¼q=?£×Ã\͸í‹ÔárÎ[9qÀqÂ¥è“Z6(XUÊŒ pêÌùyËkñvPTìj«1= ‡ š\8Ùl;=s9™Û2|™§æ:rGÙ5|FG3d©>áAi#}ØüZªˆá~âW îÏ>Œ¦ŸýèŸÝ¨p‡]³e%ÎÌ8RǪ§üÏL¨XžGŸÇvô xögNº»)èjÎv]võ9%8 p»cE ÕÖ6Ì€1îŒð~(hïð¹€Ìê<èASÐÙªã€Û<î Œ¹¨³2¼Gp-®nÇÍywf—ÓF|ûù‘T¢MùÉ¡¬`g”dýöXZÿ[Q{L`eŒBlNŸýW8A£Ýrf¾tEÛnÒ¶”¸JY¢–Ìö¨`O‡x(wËh2˜iË»ºÊ4öN,C¼ ãëÉd¸æ§›ð­ÅR$H9¨odþ .t¢MW-ý®÷ÒùÄL1²®‡Ýïlz½GÓYÃÍY€Ì©ÆÄg°1à€¾ :ï±5)„¹$x–ìF•C=©s¶Þ…:°Q¯à\GÒǬ¥eP§tM=yŠóvið¨hξ5ézË)Uä¢Î~“=Ú'­R•Ñ0—éA€2*ð€Ñtë6– Wº!ØïÒ©FP9DŽÁ¤.³ÒŠ_¿C°­µO™ø¶Êþ©t¡½„Š&À£ºõ‘m¬jÄ5ª#™.ᨠ¹u~¾t%Æòl1»PÜŠ…­9VìÚ`Ï c7#ß÷_ïÉ™2Ó‘u%ÂeIgDƿؕ8åÎ+ÀÄÜ‚ÏQŠŠÍl©×üAsÂF÷9¨WBG‡)×*‚ƃmW_…‡sXwÞqo6E5›ôB>Q4RØÞ’HžxbÿGa„bEÕƒ %CßÚ©AÛ,‰Ü®GÄ$ú·Mˆ$OµE‡çOâ7üS)èS?dºè]¢=p—…vPA½ß•Ïp.Pœ»‘Sm”©ã¿ÂÛ.¤8½Œ’_ñ”–ÿZ‘Féõàà‘Dʱ¤§ÃæáV~é‚]†ï+tN•Ÿ–¾e5ÉÑÙŠÌsS¶§ÿ"*ÌiKôÒr«ÃE™Óž›¶Õ6OFj”â*âs› óÀW^}²³wÂ_ oœSÄ4Y`û…o¹ë•=éi¼k¾˜3üdp\E–»`y`ï,}ﺳÁM|™_S,>™n õp=“äKg(}âØÓ|üªà¾k„£+™_›¾CZ'—¾ß …¤n¼”ýM–rÚ•=&s´¼§]Žl\z± ®õ)¶³Õ Uy8õu‘+EÅ.»aDIdGX]‡Êˆæ*ïÍzÐóE9Š-{Æéí"ïÓ»¼€;Çôá,º»v} Û»oüÊï¥ÀP{ǂ߉Cœ *¢"sÛ›ý‘Baõ尿Mö1Åû°r 3@ÍûuÐIæç¹ôt€|)¶­}6KÅzNÂé´%v¯pjÞ1ò¡à‡d/¥3‘A±ž-~n&ƒ:ņl1b!½ø¸¡Ë›¦bEV¢ ø(ãߦ'}%-þ¼âØmÕº¢·™’ÅãO#ÊZ¤fà± 6è;£âÌ8Þí ÈÖ¡7@=G”àìXÓâ|S*WË$ÏL2Åj‡~J~§"DóT5Ø7ÙÎ…*ÖÛ¸†¼dÐêBÁnÎ$Я_•ñ‚8gºnL\ìo·„<»+Êq¡©ª×Â.…®G,Û]?˰úk„e;¯þAù/¾kEIš™gªPu²;;áb Hô‹1ÅÿD¢¢CÀ¬Òváãrš2xUÇfl°0…Cd~c_ØÀv¹ù>²Âj8¼9ÕQ„‘ûª¨ Ç_4³sa5¹N3BÐë¹ä‚é 4‘A!öw$ô­RË*‹œ¿#>¸âÄ !0„õ‰ Ï# 1AòN |áþüžÆiضý¯3³?{T’™n¦»ÈqÝζ"0› hPN¤ÆqÇóDQĆâ_ÖÑçìÎä-q˜>}+1{ý}%,w(Øf™ˆYÐL<.Úðæ!< ŸŽ°Þ³¯!dÝëô 'ŒbegÙAú}ùt%êÿ8«˜>[bÌÔXa¨±w­åu¾Ø-áWðƒ÷Û¬è‘|­We´ª<0WQGoŽþ–ÅÄ\bÆ[HUº÷†¸'0¶—>­‡ìš Ó¯!oè,¢?ñ;<}K ¡ïØ*ÒZgXtT<“Â0P09‰b‡ÿhPY®î¶ÀþIŒIq^µÐBWfÞc½f;A—^ÑÔkë{SˆõFk¾¤déd«QAPö­Þ.v–”Ú—näñ‹«¦Ïç‡7·L*Ý4ZïÂnÚ‹Ô„ª¯á|*h¼¾eRƒ¦$XkóÁ€XõdX8º»~z‚c1­è©÷–(ü˜&,÷‰ñÞû;.L\KÌÝÒåæœñ…rþú1¿+$6vÉÙýZa£Ê<½ØÖÁ¿óq_é?%Xa­1ýŒš,šŽsx=é˜ìk÷ÉàÍs8èc7æ=ùÛ6^ñ6IÉó´8·ô§Æ'è»þ²Üm“6?H¦'zÂ. à·@Ɔ¥êŽÅÌK%¡d“Ul¤:Çp œL(¿7¯Tæê½¶AóÔyËý¨ñ&öåˆâŸ1Ðé^ŽË€=F- V ż¹xx-äî^ÖJ ê²âU£oG‘Í?< ïöhÐðƒ<å ÂK¦IÆdÔaõ5ñs%[N-á«­Þ¼1¬±‘~®uÊ{ÿJߌ ¾ßEˆêüOFg„Å$ºpZX°­B áÎΣHùîWÂQ5òô2Ù4 ˆoWX›`kæ ¸ô І0¾õâ`u«w˜ö9£†g¯r£äzèþÞ wþ_¤lð<\B ~¬Cü‡ÇîÙ]üB‚ʨš9êSíZ¯¦ hÏDZMƒ wßÑõãï-é®-‡åuâ·Yˆa+BÿC]"\.ö“+LYiˆ¨Û»Œ|ã3Ô~›‰žÙÝFÇÊ¿jÕ=6NÞbV¿s)©&Ä[1ð{PfÐnìÙÝò=¼eg}÷ ®ß½ø‰Ÿª ò“ŠjÈ JmþôÄEÎc—® TÿA°w˜-yÓŠpaߥV§öÑY¦µàXÏ¿ÎùÍ`%³üÓÎ"Û‘;D‡‡%N °¹ôi£¾ ÔöÉÉèàÕA#Á9<˸ ²j &ˆ7˜ÃÛ_«<{>ùº>ru-8í–X\š8™fVžá ÞØ°>âΉ ’´)ææ»~2sÌÀÄ€Úîd-2ß“™ùéô½´ûŠYÍÈáEœCÊ5¼Š,m…âàF÷^|ul²1ê~¯hôÕâkÊ»çy“¬Q}½$Áõ „=­·‘ÇÃ@z4Ÿá§Ø™e™EqE—_Åo(•Úo{2‹Féw‡ÏN5_~;õSòÞíÓç~–Rl¼ÞÞŒdͽÝ/yü—ÕÒ: èë‘ëýt½l Ül½Ùµ»¶- âÓ,¾ö(%â´N:í0©B µŽÆczï÷XŸQ£¼ß5pç%;Oá’Ír%¤ÂSoüôDOºö)x4\E¬ÿ‚7k“QÉ÷kɺ;‚Òaw­©Ý‘Ô¿F¨rƒp Ñ•oÜ$Ì\þ“€Ú²Õ¨ y›þ¦[çϽEð;éé®»Ê,…lWÿjH\°¹µ3—ª\ÌÚBÅëŒD´é«£Z2 Ëû™Ó½ !hùʵÄRFÀ¡­LÓ MÎ:¯E@‡ààÐqúÓU—íÁÛ7j£š ]8‹m½šBßÛOô7ýBÃs›5dYb¹2i1.ÈçL2ÜZ©èÇà ¼4G Æ&*_Z)´–*nŒm9•|v«A§T‘ÁýbûIóy±ÃèRøkO…&;]‚ÀõãÅåêf«Z-‘Ðù)Èex`-¥ZÒ™¼-!oÔø™-4P¶-z^T®¬äÓªõÇüKZäñs¯Ûí!~N¨3še“‚O_fI3wèC«…pî=ÊT·퓺¬T~y¿Ôâõ#m%÷6³ò)ÙM0þv~ks–ößÐnÏ›Æb[»* ÖN::ö­ÈÌêÙZ›ÊpAÆù{3¯DÂ2Œ;º-ðÇ~q4ÝM½uù†’–˜RêSŰLìhˆtjJˆÓ§„uwRÕíöÐ×”ó£è]ä´( ,+ßHáÅ;ÒSo æ èEYIo¨²ñþhA_?½[8͘~ê9±rŠRÛŽ.6ͼÑ=3µÖ‚‰ïíÐ.æÅ–l;ÿ%ó=át- ªµl}?°|-¢‡rûe–ð³ŽÇÓ†>!{&:¥W–‰åãyošì=Q<Í,=›E¼}±UÂr‹¦dÞþ´j$í¦¬¸_pˆšž…bÂÅ•jPÀÐÌ‹2z<£ì“†êi[V€Ló‹9–ˆ_6¹Ö¼aó.i& ÝÄ‹ÈÜçúÝ]ÆPûÚ²aøƒnV÷óÕµ+9: F )¦«¼Qó»Õ Ý•ûPj3Ⱥ~éN, ‹õ(–(O•Œí¶©$a—•6ÍÿT÷î÷ìa­¸‘ë¾³ Ø8P@|AϬ%‚p­¤â7숋^7ÉO~|™ÆÇЉ*"åðݘì'Y¤ºUî¹»×øqÀ|›~_dÎüêÙ•»´VèaÇõÜO^G`›)ÌR<m|ü.“È0´:ë/0Tˆø}e‹»^ö ÅÑð&"‘¯_èc: ; õlv˜ôúÚ„žV‘ìö`¬ø" ÄžtsVQg"D1°‚,N"—‹©Sypèhý,ó¢aŒ‘]¢D(ÿÖ ¬–Œr²¢™æ¹K!ZK‘Ô`ç-<>R…Ä “c"œàx=¦DT»ú§U×.lr˜bÙÑñ”.*WñQ úÀ Á§5Ô±úÆ-ŸÂX‚J ”`ƒªW*t–H²D¤egüú;áŒ^¢+7úm< ˆ“Î3XVÅ`n7ûh)?1ÈáFÖŠô€ÓVÓÇðà)½€.5©… `x_9b '5箚aÌ3…Hƒ4Ù‰ôŽx´¾x¢÷>çOšÍU!‘ä2ÿ¸wá¤ï‚á«"]>±3iüÑÞH ›`È&hˆ,Aÿyhúž½Ç½Í€”‡«üÌf¸-³ÑñBüF£WP!3„ÿbbœã„žJƆè<^ì¡Þo¼U#Îi¡nIÝÙ§ÍÓŸ_®´¦#p”š9 ‡sëk±¾h\‰šçM8‘–µ»“þöii‹ƒÖ-FÌe–”`Þú RgÞð7âÿÄÃZïhŸôÙ»¶äL] ÏÒ£>¥=……¿ï#^ÉŸM͉©™r÷¡ß a2Îi¿I%({vî͇ٸRž ÷¸ÉeÿV”ÉvïtaR·ÿì6Æ»Pi±òЋôI·Ö ‡¬°{Æá€¾wKÔ¶=¨¢aR-fXà—?¬ÞÚ˜b}vÏ–¡}¯–›Ãcεô—¶#6 SâÖ1šæãbVÇ{~øÇæÂËÏîv7þ%Áñ‹€wðꆟ‡á †W½†Äe¤.=*³§ÙÜ–(êçjC™nB#íÀ×𠂇þKa^91¡Ê_]bD4—¢ÀŠ¿tiAl5ëÍž‚¬M<z+å¿S@½ÀÓÁ7žÜ³Uw6¯}쀡‘üø;«Ré!7åFNºÒVò¼ 컂GÚÌR¹Ÿ¸šõè,{>.Ä B–|Ðþ– ô’•²'1ל wÁk¯öGLy…̼¢¨Ò® vQ1ÃðÛo-WùD»?E÷û™Æj·Õ•A&g0›µó~:`©u?·—õ㸱SFXV£Ù`p•¿÷ñY¸KßÐèÉe âG`]­\,ÖLÓÇq<ö¾¹~‹»ü >Š£…µa!ÐNLÕRë+£rŸ‡ú¬$¸ÈrN¶? ²ìsÇxEÜðÏE+"ˆÍùì§ ‡öã¥jEýN1 ƒõߣ¤ø“ÞsÁ¨î!vðÁÑ Ý¼Õe+©.Mrb/2À¤Žá!š–'ÂÜ®ó+댥4œ•mĊÈ_ðÿ=àÒgoIàs„ª £*ÉR¥š¥Oúl}îyÐÑkÊÜø1Z„1ËņªäCªíOÆ?G4¤ÙDÈ“hä©CùÉÀÏí«ºö­û7ùD¯!G óâñ>8Kd“?ôÛË+ÛÐÄp€ì®!Ì /ñ‚XÑ"­ÿO½ÀÛè--\§\Ô¼'/¯èv]PÖ§H`m9½Ç°`AÒB‚¡ûôRs?ƒÐx³íÝKÙ;íá ðÞg‹ÈzÓeÂMTy~ŸôcĉeMÒÜZX[ êÃq1ø„ÐàŽ›Æ9Êqypî;;Oýj}õàŒ¸HQ_~ݘû]ä¦xóB#ÖýLêZpK@¡P1Q$ÍåŽPövgûÔ9¯_4@ ®YQ§f¨ië^ÓCH4&|ÜUý}]í “Í—}O0¿t,Aõ}$w€»Ðß\‘x¨Zª:Êù<‡4‰„Pß,»¦Ç‚´ ÁŸ Ô˜v!>5ÿ2†ËÓžkÇP±ðÓ¨è*hRâÉ!o|ýäºð.8<ó¢ê~!ò¬ònšÌÁ_Q ‡Æ¾»ú¥øUŠSÝžªl½€7=yyrD  yÜcg "å¡/?Ñ»’ÄS°fšøŒvÇ´‡øÞóÞ9 4º5ŽÔÚ¿GqW(g%Q}o"èÍqâq¨PØ&…âê8è'’°ßÓ Òª–é2f}¾Þ—ëO‰÷x&PñR±ÜI,ÄŽ >£ ÞîòÏOÎðÜs2»SXæC xåÄRð‰,ÚˤUh¼/•mGxÁ€€m+öÐÀ³Åóðj¾*$“ày§óTÃżVÝ«G91ŠZ×ù¿u‹PØo8Ôv ) q!ªñ¢Énúä ³üùzä*·!®d´|ɤý×G@Bõ$.”Û9KZd/}¼©±ª ÊW_Æ#› UùæVà !T7Â%8£t."¥gÝ!!F8¾2©t´O\Ñ0ŽgfíkÒ´êñˆxƲGšaÀé^Ej§N°Ù³u3É9ðøo ú–Rß7—QÙÀ›Lƒ¿:pUºÇR;?õ¦ ç·®¢\EŽýЧ!G!hê¨g:uS£ðk$ç­†‘}B4„)N£§›‰efaqù‘“UqqúvÀWŒëßEšÔÝvƒšnµ/Ñb`ö\]HÌ z0ƒ¯¸O(UŠDOo*3QæEa²0]X1L’Ú&6þÌÓräG,ܤ°DWf&bú¤Œ­Óˤþ‹Ë3'ÃLwWwç«ü?„í3aÝ*g,°`¼‡{d*ß*ß9]ˆTí—4=Ñ󙀷e/‹TÊ+š<“‘ÎÙY}[sE”{-7÷˜ˆ^²lÅ<û°¶šˆÈãÊú`ŒîG¼¸—mkA뻜±@:¹*­†Ž›ù6 5>ιÃÓöz˜¥?þ­Å"¸Ù€ÝÉͰPÅÕïÈÆÈʽʼnÌ9µx1œæƒáæŽÞ@ò&˜¥“rõ>Ì[­Â ´3§»ÙºPj”#åLð/h#ºÕw1‹‚'8:9Ð ”ér×O²^íUÌL'÷q&vÌ÷9è|1ÑK eÒ "K•pZ ^ê_× zU ÉÛ5hß›aÓ™ z4èAG¤,‡ôV½¶v¹”{FRQo…Ü:PÃÊG7OËÑê‹È ðýEÏC3ËIûfÜqözç*I ©÷Þj@«×‚5CyÀÊGΆ]ÙížH¶Õ iì3Þ‚ÜÙÀÅBœçóM—ò_Cl¡=ƒªtôj)]Ÿ·% u:„,Ä^ÃH üK¶á…¨:ȶ“jq„…˜È­?©¨„'þfƒÌ§ýõƒÅpͪ‘‚¹ëä­ˆ}뢚]xЯ2隻̔† €³ƒôÕ·-;Û¬±g~›+¬¢§vë—MÂ;%ë´8ö[­µJóŸªçJFÝ·Ü”wÚK>šø|ÏóÑ዇©W=­pë±ýYôñŸ¯LA‚fv [ÏM¼`–øek—aÁ§ðNŠZõ™ ʹèb½ÃRÁÆVâ‹ã²‹éØý–þÜæ¡ž ^œœ¾\"nib}aÚÔ\Å´8†›\eÜ5ÆÙ&´l ŸÌ—TÖ!3ƒêÅï}%O•ꪽfNE2ú˜?@ÄÀçZ¬ w8? 3ûvt'™,¹¡­Ž-9ÿP¾C’äªkP73öMÓZ‚þœÅ™‘Ñmú}ÌÑËŠ¢š9— ¨$oFRDf{°údþÏ¥Ãñ‡•2dõꢸáƒtM0‰©ÅX™Y朇'7€¾_B¨3bIè†ç³Å‚R›EÃ;••¾oɈ¸ç ƯC4Z£@æwi73ŽôèÕ]ððuìážS*>éðÁP§7¼ãr $¨È“ÅÒFeË}n÷~Ë·Çyä5GÃÕÜ7,Réüà=‘¸]Iýçʸ· øeêJþw˜C¹•¸ú™1w!•äР$s "ï’Æ-Y^ðìKOR]·DÛ O¤a&÷C ™BWÅ”JÂþÆwªÎ冈Cùc¯_ ¯ñ­ÑOŸko—¯ ¾½G¶Ïy½sAæŠÆOQûB€%¹Ò9]T:Á yû6m+´ *¾Ôø_&e›Ä~m”"+ÔõeÂVúúó%÷ÈðsÞ”)}ƒŒS)Ê/ªð¯‚™ÛxÂZløw”RE‚ƒâ%}ŠûŸÎ˜¡)àØø ò­€z( ²h8“LGV#}Ë„²ÛÒ]M$Ú¸18Ìö€J6ôñ’µJê‚ÇCiU×Yž¾š¯ÁÒH³îjmÀD¡Sÿw>ò÷Ñ~ÜQ-Æ5•÷ÕZl̦æ.6M>y4­V{õ;={äÍ`(f;Xj¦tf˜»©Ÿ™m'p‘lZiöRyWK+ÕÎÝçÀ2[šõƒM±7i#áØæ7?g/Öv+ßk½Y‹†J@ ¸Z!Fö×)=ªÒ Ú6jˆµdnÁh™Q‘"ØmH’õsÎ$ŠI¨C×W¸][þÚdðJ¶öîg:ú€h{`öZê*Ä /öGìÿ€pN³˜0nhœ¹óŸ ÓÖô>ûKøòü6"ÖY¹ögÿxm²¶:Bn†m3È©”ÁàÊT@gv†o‹Bb®kĈ|&Ë´% íâêòëÿq{„Å@T‡ UÖ*‘^l#¾ë]EóT¬ý ÀOPÁî[gC­“n®BgÚÂ8V LM|r§v¼G+šþA,G>Ä ¿R¼ÚÄnq„–W+ŽyÎOu4ÝwŇo‡i·iZI¢àY}ÕT X7Ž\a͆£º¾){6€ß—4?û‹)Ç^i§7®S ´2_æ?7 Îj»ð}þžÒ‡IsúuÄNpkJ`&Ð rÿ¶ v^T\QhöÚ™KÓñU%¡a É6У&ýz×ÈOç:ì¦r>£5Û,V…ÄÙñ™Gö AsC7ISÉiÑekÏ0B´Þ®8 a §’4ÿ@ZÂÕÀåZ€7y…–‰ÈÏÁËÛ”é°QŠdS.©%-ÂntXPI R(jР¥üò¬ZûG´õ“°ÛZb¸Ö¤0ËE–6Ì^á[§Ò >~0£Å1iyµ¬Ñ Þ=ð†´è×Iúc™_ ÏB£{»åâ=˜czøÑÅ÷;€Ä£)ã›øÚþÙr² b¢&´ìúÆÂl9‹úõëk„Xª¼ÐTäXhä·\[ö~Ek\Ñœfó -' ÐrÿrYî|ø&¼GoH(ƒÅÓ¹†&Ò§ŸÚ‡{¾ùž{™.WŸ"0=AÇzƒd…Š–BÅ ó'ÿê™sâäÕ1õ´Y×Õ¯öˆrh.#ž_B°â|Nô'ê»}n,f»¬#ÐÍ­·}o^mseÆ™ENɆ£ØW©¿fdâsäóÒcA5,ÿ>¯„ðÉꪻ‡,æ^;z(²Ù ço«À%ìÜKõa¢BñKJ,b&ꦎwv Êï Ž¼â«‡ß£5©ûrð Sæ¾5÷É\RWùö«¿H¤þ"ç(+?â13Ê`ä(› ‡¤A(íEÅRJjU…÷1ú™²Ÿz‚„ö¦øjþ]2èÓÄÉM+/Rµ‹áÀƒÎ౎¦Ÿå×, ·Vy˜ß±3«ù‹+kªH—š#¡þŒtByøêas |I±jýO¡ŸR4åè_. ýÜbö¿á%WŸ'ÈžiE©‡¦þDz Êg• qpL·ªÐÒ‚^x&ovïS&jßT“ǨLeÒñix Imhï³–+D2ù†{¤ô&å Ö÷Ž} œÏ;?”õ„·oôáŸUˆ¼>4¡.p™D’4‚ÃNw ë ó+wÓÏCãFeRÌp8»¡NBÝý[ºcntB?Óm_·ÉŠŸ›ºìÑ6z‚NJ=»zLJà®l§ 1·ýƒº[£ÇÖÚÜÛEcyÓÔlû )M—Yû°çu–tãç‡ðsŒ‹€ÿ3ážþ•½Ã¦uj8 ¯£T2ï 3¥Túê';N¤ƒu0¯!…¥Ý`ô®%Å„ƒ\F‰ôË‘kÒ}óþ¡üFïžÕtæÍ{;ÌevMÓ&§ƒcÑæj]•HàÔë¹×w|Ųà P¢ÓîØŽ¥2'¶e.Y¸âF0»‡Ž1w°¬Æ”ŽdV/w1ˆ·F4 •`åš?íÞ‘ Ñ®P¼›ùh߯Ç×6B;v“xîD¬çö·Ynqüÿ4Ðú4N£À Á˜ÈŠ_µ2¡×9è~c¾@ÀËÞÆé¡¯m¢ûtÀÖp_%~nÈó»@s«þ0Ã(îG«¡cû#²!ì:”c˜@Ú.¥Ôl†äOJÐ=àz´Ê±?ýGéeÿ ã_4l@ÓÏ&œäë¤Ìù_ÅËŒR8™ð’<ÛKx™AlÜxB/ü· ðÇÁt™6üXÐLøL“f0œì±†wAsÄ£—„®Ìé¯}7§ÔwH36PÍjÀ¦øâA¦¯ý̰êÄÂiŽùtê9í¡9ÊËn¸Îô¬ Ü×ý±ó,ÌŠfDµüõÚ‰<ç^ ª\žBÝÈ]A\ò/ý&YXtVUO‚€›ù¢íÁŸ]•Ý€xí"xõ÷ƒ¿·ŠÙÃ0,èaÊCB“®©òHQÝiB‘Ý÷çÊٳ î]0Ü´7Ô'1µsÇ+HÕ‘®q0^…ùè_  ,ü`…ˆ(vJÈÂÉ4âc Émw)âα5µÌn{Åàu៤–ðŒShÀ)EÖT*öC\Š+¯hñɼ–‘¦©,o(ÔLanÄK™QTÎk³ßÛF.÷±0¹Ùž8‚Fzqò%­¯hìl#µO/-lö‰;°î'¿¾ïz€RÏ’¥hp„–¤ßMG- >xb…YÕvÈ~[|S„·ÃÇPwîPVt‚dÕõhiKEûôŠÜ)M}O‹ø tÇŠzÁ‹1…k¥Äá“´u8)iv(:žÑ]´-õçF??/>ß˵2[ÀpÞBAãήÖ"–BdH‡ÔöowÈŸÒû.ÌhÆ>ÆÊ ”(ä ƒÂASUèåðÇòR¶#ÿ‘QM_^ÔâTJF”-:¿fI]UKLÑÑ‘ˆ»l¥á KCVÝì¡ÿõ¡”ÕÆ¯qeuËRâ.þŽB¿øœ„RœR-‰qµNäµ±$%®à.=R•Ík‡Ì¶ÿyGó’+þO1-M£vÂ;.Iχkì?Šñ+ò·xê¦-¨·¢+8 gC"^!`\x’7í>v±nK¸äÄ“¡5®æh_÷6Å 8#\žŠs5‘O˜j(Þª„ÅM8Àt#ÃŒTäT>Ÿ&?Å57¸£†©BÅÆÎ1Ìï·yZ œtC¹Ù9¢o…Ñ«‰Žvµ(÷ C¬fJ“Þóæ“K9Û?‹‰Ž»ÈHáQ‹ÎZ`žaåMq­4Ú1’;F$ÙªIÁ`\ÓÇŽX³øÇWÿJÆç`³åaˆiô`sá§+Ã[Eü° /ô)Vwÿc`Œ½Ýg\”´ºQé0èÓî¬ a¿3X‹Ùå²WK¡O*mì™ÖaíáÀå¢åUâÎõeX æEg••þ³©–T¬ÓÂP†®»9 <=m»½&Ez@ nç/Çc8_ÛáASU)˧6#‘_‡Ò8‡ëÌØ£³ ºN¥ø1*µÑb>‡ÈÀ`ü$»Øá5"6ÔúÖ¿í~‚ó ã!æ¶{²ñõTw4æìÂ¥\] ¡ç¡ò|P¿EáüB6þ‚’.8 Ç5>”Û¯`§÷”;µ_¡ÉuW„ZóÓ*¨qô?=²j~—s }vâÇÍ2ïçõvލçèqvl¯Ý‰@à,˜k¿]Áþím—«zJÕ ˜º2 ÓQqÔ€—Ü÷¬wu˜OÝ9ëÿ‡Žî’0bfÅ$c¸òÎûÓÈÕ*ö×åôƒÄ‚p×Éü‰ò3<äŒTtÓÔ…²:$ÆÎVºcañ=’Åò•ÖÙ8½HVYœÓowË6ÔÅþ‡Ê™×Û‹“Á,_×À ‹'8ó×tdgVؤþ.¸P/!.6Uæ:â?çd;$pü|}Ôƒ^ÌXÿÝqØW5hMïa1'ÒxÆ»´]1ŒÉ‡ŠÍ¢QB„¤)Qœ(…—hÒ4~PÌhÓ‘'>ãÛ¹5…hù¥3È(Â=¾^ÿw6Øî¡¦Ñò&ku—»¡êw=÷Ь?Ä*͆Õó67âRç ÷>´fs¡Ñݨ’,©ï%£?Ù›»É†I¡m0úNÔs—âÿ8¥5(Qd<ÏÂPf¨¶ô|sñª½ÄóŠí`¸†!~òÌBhµ÷õŒsL¼UFúK˜1»¹Ê/`λy:«u£¢;lX€EðýFPä*š85<ä[–Éh÷Àºó-V6 }sB7ˆœ•d|ªµÍ¿Ì ©º`.Ž~XõÌhbšæ…òœtz±mÞHjEûÇt½–(É)ßdë;I4ˆMÔ×ýË£9Þ’l¬ÍÁb­}¤_¨¦Ösû"«#^ YG$»¿O‹`}ðõË& >ž4¹(Ûox²œçi¥øuÒ\ÅïgýLms”¬™hô£!3ù7}¡®´î@³ ¯õK`#Þ#̵2üXùuñ¹]hÂ*RN'&¬&n÷Oµð}:Í}< Î _.q^Ië+àç—ÌÊÝ»ŽÍ¦¿ Lfa‡×ÚÅ„ïáRÉ|†Š ÛƒÐh(bkДØ+_T@V˜Œ¥*÷iæH „8ß.Iÿ.и‰mh¦ÚYÈNœ'fŸuyÏLuFléÁ—f+ƒ˜“äxbúªÔ¨6ïò‘âÑW±RO{Üu½bþ]1ã»taE=÷X(“[³£(—>‹@n?Dõ;Û(0ÎU ºEýæµd„U8©a¾Ml¸ûEÃý^okÞÜ? ª°Ï‘ȲóDè\.loüÀøüù0šƒi0¼0_z†h¸Ü~ysó¬è ß¼ª>Iá6û_m¿nC¼äe˦uVÌZhñÛWMÄu!\¶ŸÄÍôÃ{®š!Î@Òrs²’ù𦢌¨L&ê…߇ÿ67(FÓñ`FÏðEp2 J’g«<ÊÔbý6Eä>JˆÀ¯êW]j1;*’Æ×ÒÖ¬«vZµë½ënVÛ×5ÊÙóöûh,®H™ÙtÍŠ‹ UÇ19dLR=Aʶ©ŽØPè_è ö[¶HÄ8ìgdä‚þèù"4ÛÂtèÓ›¡asN2/gÆø®ºÔƒ•}öÄ¡¶È?f.ž:ªÞñÃo7¨Õn«±eÐ6ŸÁäcP`øÊ=?9„/£í•QÆ1¨êº³ÎÝbT=ß¡|ÜþïÊ3=ïj²l¾ ™Aµq¾¹Dœ‚¥Âc‰§*D ÅÄcŠtKVÛ@%žøv¡Ñ¬Nÿ*?ñËK1{ Y2­E²“¼¼w®ëÍA(Ð>CBÄô·:èýÅòýÚ)bœ¡íÄ}Vi¡›9£V\sL¡†DdžxèÏqði±ÊÉ„J\~bÄîcmCÌà|Ç |<‚tÐ  µ{ÈGÒ¾Õ¶^o(ñ8#zN,ÐÊÞ”È>òíÇIÚŽuÙ!Hˆ"ï× z×Î&óW3£x°Ù^OYúäóY™ÚX´qžÇÞJ\Ïw…ãêÕ†ê±óÏbn·&¢<Ž Uü!™¥yP/]l–L&Î|ó!VºÝÒ8VTÿ ë"èƒ7É‘Hß­&Õ¦2¶“‡øWÜÚÊâ<\OÄ`Y:fùž\$Käõw†ªüp³0¼ŒüÖ1ؼeû|5 ´ÞMZ7–Y;Õ @òäá5pGÚ 8Ë®á–ý,lÙ§{ퟸç}á:lIõî‰$¸ÿWÞÂÄÇ—î‚[§xôŠPÇ h[=Ù}%%`ÝZ .O^QñHu®ˆ+™šûU/j.7”.©˜yÄ8…Nƒƒ‘§© Ò‚Ö)¤?Æå0Ïá[W–¤àpôH(J¥_3ÙÂxõ^Þ GQ ÷ ƒˆ a<ŽÚS€¬•Uï²v_EÃ-B(f,é@¦òèöÍšŸÏ.†ä¨§wÉMš†Bc|¢«^üQVCv{"°Î>ž„µO‘Ók‹XHüy\Ó a€]GÏ,ùpïöAù!2Ät|ý¡J{rð}˜í¨±ÅÂq^® ߨxÖŃxë`L00ŽXŽs$A0AÝ{Ðh"}ÙÍ’ “ØáóŸ•6„uòã×øcª`v£Ñ}í±TWqŸö–÷h?¹æÃd´â!Iž·¤UzžQ[Ûe¯ÂèÁµù];òf©tø 3Œëdã–yv¼ð²ðG„o£NcwÜŸª3Z´V÷¾¦cl :‡X1E»‘ÇFª”ëO€.ìæé5±þd‰[â” MÍæ¥Œ>ù]Á î×µuAœ|!4bè+¢âèGß¶æ;SäRá¦ZÈ é#9ºh.&²í„Ï ÉQÿjÕI´ÈÕ)ùc9Y—.…¥Óà)©¬9P½ SYó³|Ähý¸9 Û|Ù^ÅÉÊW·Òp¦whªñwìô¸ˆèî)p/Ñ‹ø(êÔ5é2OºÝQC~0LN QJïÂÇ¢Nq.]µ´ ¢­³b$gc÷¤eµ/<>_þ /At;t6–ªl¬Ëö—Ý-”D4ðç;¶?¡ÄâÆp‹J©5 ¾v0qpÒÌov›YÎSèé‹ ±€!»ðÓDh“,?<‡N“õ\öª1FH¤Ûd.àa·Z7¿8J)¸…[¯÷Ò³4'7`»®ÀZ¡bc#"‹‚3äQÊþ³6€Ï¶ðá­ë¸æãͲ™5jÁGˆ/,~h§Þ²°Ž|X± òº]¦›­½ò]¢GMsÞÛ·è!ï;xZ®¿æƒ-ÖA6€1ê´€6,{:1"×+sUǺM$ü–ÊϽíŽLÔÛ)E‚Z¸­ku­ˆzÁlGMo”FûOFȦE­eó<5YVùÎÕÚ$-ý ]¤ÄîïåuÇ4G‡ý&Z1<œ[ê‰rP¾Éès< Z<â ’fª¯ô®”œ'y¡ çÿr|‰$»0¾×uZ÷•Š©çœ°‚§sšX½¼²¶k@èòò “É”ÏC¡*]XÎæêÃ]8¦!7ˆišXÀ¼ßg-ŽÈÝM› Õ”Díð^„_“£¯*à ÿy÷P~È:p¯èòÇO(‰¦m’ƒL˜]é*0AúšÇ†ºQuvÒ)u%«Óެ/cj†h'©®t²vfÁ§?MA0Ç…ªòxEtI«E åÿåv©ÍçGÑ@Özw.×›7ô]H=ÌF´zîÑqY«òòkylû‚@-ד„™zÚ¡ÃÒ‰’ Ein–Ö˜¦Lk`³„ÞuDд~ΞD£Ûñ/vZy×Ô‡K"«ðáÔ„Ù~ê{ì¹åÔo9‚ZßÒ£Ž“I‚¿U˺HÞ´µ$˜Z»y¶-nÛ ñ% 7@ªˆóuüñ’¹ƒŸÎtqZãò-¼UP”€1´ì‘Í_<9•·ªeSçõkamsÂ7vúdÇ+ÂnÈÛdvÛH³mœËJ3±¾N¬ÖE\1† Æe–†ƒÑé:•æêÏoN¡*kS®çðÏ9‰™¶ÃØ 1®•l,¾W%‡Ng<;{Ó뻌IkõäpÔzˆBS¡=Ù{T°>¦ëš;nÓ¥úƒåY5åŸÃ©¬¯Jèèv-[n`øT6iΕÀÂoãã¢Î(6Ä„dñÁ}¬à¤. ½1u …¼"}Aյ뱦¿ÿaÓVˆ—ºcoatâð:à?Í´DÍËÿ-û­ßMIÇ8×))`^ùΑë r*4 ïД‚]7Ø]‚ð¨ªÝEF§};@†ø¯˜T^ÜŸpYYhMÂw¡èTxjæ‰Øüoƒüë¨ð' Ñ>äéF“÷ןi®iùÖº÷7u ò>Gݳr4üÈFµ,âÀWTÌ› Ÿdš¯]Gñk/s„­_À'Êa^‡æù¡¦žÕÞZ|SX7¡M© È¡þuéù èx—¯kºáÒÉ»¬Á(çußY2VlMÌu­(¤ÙSrI0ò‚¸JJñZõÂJX”ª7ÕÌÀcÞ¡ŠGC“ýø÷RÍ}îNÒ«úI–@³;{DD.ìÁÿtмiÄ4KËôë™…™†O9-´¡[’‘.AÑøæT¬ñ¯wé¡U°sÄ÷ûÊÅ\¢š—QEá›äg§öóZ-¯üŠZåÊ9ü+O¥LU^jÇŸ«Ùï0à˜ëb‰uOn– —2Åž…Å´>ëUÚB)¡~‰”YsŸå ŽìDEB×o¦Þ ‰âŒ#mÏû­gõ×IH‹OpÏX‡n‰ùÒŒup¾>êí`‡Ãmõo6Ì1v>r¸‚g`CÞª¼Lš€S«e±Uˆ½4=!4K{õ)ÿnHö¥?µ% þ»˜¯þ¼.HÚª}#@—#¯ü óÅà Y©x9y9q$;Ýæ‚ R‰i?‰"\6tç·tRAÄ:Y8 ‹^ݸý4ÁLƒ?.&n85y‰Âoö1€Öfïe,­bL”êk ä_#¯´½j„¸ê;Æû£O4¦G÷t7®D6>Pœ¶¿Ê°Ã¼ÎÖ}h{¼›ž´Îñì¬Sò± æ^î\fÁwÜÃ1ò%ß{|lÙ7Abj\ÎÈf]Ëgìïz^†B3'åGùqsgýúŸnAå´¾ÞdªHÙC>ŸüáÅ”D-Ά™Ý´ä¯ÇÞK»ÞÝcI„)oª. 0ÿ2¡Àq1Yçn¢LäÒÓÕIzw‘Ä0,$)6è³çõ©ýA3¥©ÁÜ[LX­ÏO¡)Ôj›—õ[ ›ç%¶Pé¶]Ý®T´ŸÊßGQÇtÉ‹V·Ç-‰­­ôòŠi<¶A˜qõ·/]Þí4ÑÍÌ#³¸ÂH‚™.ßC$ª³t;Ênr?ŠXÜ7+¥Ñl–¨r°¼ž:³n¢Å‚ir©KÚ¥µ;¤o.LÓž;’’û{¡Vu›á“ŠRAÞ¯ùùe›!ai*‰¦ò%¸>ß\Á½÷↧5¨¶Ø!æco¦úÂT—¼$™)}Y³«:ŽìÉÓ%³³€Ì öÉeP•í,/«˜’à­'Ìd1 {œÉv»t*Q×…ˆ{k°^Îë·ôD7#PµÂ“Néù"Ø;å –®¤ñ6˜©êlo~§eЉ½ψ›ì³«ø'~Ed@k•ÁÅJGë~§9¹[ä"ŸûŸR· ùÑ'μ†¿ül’ͽêTÄ2T)ÖÚIùWМ‘’•`‡rW¨Bbú3¼Ùœô¾.œs®ç:RË/MS_ìbÇsª6Xÿ1T{M±]8‘Èî@„Ñ– ¹›Àj Þ/8öw‹wØ^œ ¾L[ö¡\G¿./?ß#§=~Âa—ž&~àT¼¤•\Èèuìõ‰qèøEøZ—9Y¹bøQmyüE—£ÜÌm÷ÕSÂծʵÎ[,éo«!`»MdÍ‚ú2(÷ú%õ¼„åUY C¤‹L` $ýàÒS}Äö®ôÙ>Èm0£}¤Î+hFf|îiSLn˜i‹É°uR˜ÅG’/ê&kµIV ©x%ø4GòpåpˆÝ8¬v´í•¢Ë"Ȱ•—†zayM'\ýds¯~hÕ`‚ìü“r;ÂèÀ9¯èÙŒñ.¾ßñ~¼½~ «q>ßáËK¨ìríÆ´·Ð‡šÞYˆ0¶óõCÙÓqªîæ¬TÇ´-‘ŒºÂŸ8[¬+8¦Êæ&3tf¥œSõ>8¯!W=;Ö Ð2iÏ­½]Î\Æç"ð¤°(?±Š¦\j‘­º”ß [¬XÿqòB|4*ƒ;Ñ×{ˆÛ»¹zÿw]áðÄšpFÇ9^F5_ú‘½…~Òb&Õ¶¡–…R ‡Ylw)/’8OêÍ =A7äœ9·§ƒ_³ô‚%|| F—µQ¸ÉŸ •³»¢~nÌÑÉ{“©{ë^1„ο¼{š\_ó¼œ^àώą̲yõ€} ¹"ô‚€rõTúzŽÕ3ÙÉÚuúBAÄcÂeÄ–[`¥QY’ _»&JäT½$Õ¡¦?„†q”Ônn§|·çcÈ¢å¤ü¸ à¹ùåh×´ÛŒÈ*óäÒ=È)ðôsŽî‹j«Ö’ûì*­8†~n%öÄšøûìæÆ"&å¿dîñV|òÜ¡Òú3ž¼z|OW½õ/¹Ô<½5µ£}®ÖZ:Û:ýÓ®'Ce§‹ØpCÆP«1ZŠØæÂI©r뎷X0‚:0j«‡yp¹¾w¶€bP©%Q&ùxdÈqÞt²#"Ù3Vªâ?ÆúuØÖ›þI)2c¤€öŸ•5o:¶Ù_ü)©¬’3þ_P‚„é(÷|Uáa|Lîò8|¤’˜#!˜©#K-.ëFïWÅõÑÀH7è¦R/ŠƒðD2÷7wÄeÇöõ†ß¿ ç;ØU“qË;ó‚-ènfÉéÀ™›9åi‹š{Ë#!gkn—gôödKVÇ3°'å¯ ÒK~å[¬_lVnf¤¹@øá¹0MK“¯ÝJ öBð™0gHïÍ3®HlXjZý7~¤?5ZEN¬Z˜q•ß„²Lû—‹¢fÞÚª„Aoþ€¾ßJŠ$x žÂRunQGî¾;Î]‡kΩ‹ÚÇŠ”ݤ]„“Kº •¡Úw•?>5¢1[ºe©2|æ•®ŠP)¢Ö ‚Ð3Æk‚9^òîP^^_¥p/”LQpâöê?½Dt\S./™Ä°þ¿£kS‡Ó6¼@hÏ…g[zמqC¬õ¶O/¤ 5‰¡©¦)ž.Èž£á *#,|ÈÏk·3¨­3°èïpP*‡ ƒÂ´¹4Ø«Üb€Vriá¸*^?›¤iÊrfˆ»ˆcVn¤ÔsÈ®jhˆw™zåi1ك‘2‹µŒ„ÞV0¼0Ä6Ù\)þ­Ÿî¡qú/>T£i „ÌJ«X±hp¶Hó ëe{·Ö­h8ÄìKÐoRn’1élÊ^à¿ÿ“íêþ! P»hïÃöÀÙ-ï+ŒmOúc–%|ôëeý.Ëâ¢òi wïS-/,N/]žD½/Ih±ðšÝÇøu–•ëj~¢&4Q…;@ŒFM*)œ^œQpahÿ0/¦vÑóƒ|“‡%;˜ýªFÜ™ˆÝqbz’õLEô?³ÑVˆvÃö³ï;ÂZ™»ü‚±‚Q7–»õÊ/+RwRõÒÚádt>û§J^á‡kYÅ…ö× s _9:eÅï\ ;3w?+“ÿi¯oòñêjèúÈÔ†óâæT¥[–]ô8Õ¦HD|NX:i"ÍYE,šG—´2ÔQ®rטÅfA‘3mFØK©à‚€,G‚¤¼Å@›?.[Y7/ýÕ€K[™,ÏÒYu„Ú[Ž˜M"ý1š~íÇð ¸ýdÍ'¦}#_\l± Ú\übâ4£°Ç,¹IðÉlÂ+ÆÏ!>ªë_Ä&«mÇ;ñ© !Á©«"èMà¼I]ʵ@” ¼·M±ƒ!¤$ò!cª³ÛåÓY$ªêE¾)aè•§yázæùHl¼ªœŸº_¿+EbÜ_¿¾1Ú±-,uáªËŸH–µl[ŸÄù5â ²Þ®WièßYgÚ<„”Ç]›Ý˯¨fx\ôT&‘Oòÿ»ÚpZ/^AaJføHsÞ/áÆŸa1£"~\ Ç!çdyS\ÍÜâL²ˆ‹oس³Eã%Éœ4•™"ÕCöç‡%M]+£9ºÑ¹4‹œd³Lè¦îÀSFJ –ëY!X‘Ž‹Œñß÷_›gëÑÅgÝFZA½ƒÐWùîŸÎhÉݵºáKÇ<–ÍNäx¯Á]]Ä$4±P¼x—u$ãµÙÚ%â«iÎÎÏ$ñþµ>ùaæ̧¦G¡¥eíyÚ… ÍÚ»Ë$2¥Õ£”aÌÕ ŒÜöw0)CܯÑÚOevGÀH×%„•tœÛÛÈI#ùéϽ5RýˆÓu7×uú}›qojxŒþøRö¤,W|«‰ƒñ\)ë ;ýÑLÁu‚ÖtÉzMÃÌSÁÈ@ŒéÈc0 †ulnê.õÃäCÛ{%²3&µ±Oª²ÄKÄ—ßÕ±“Ç.¦ŠÍy€tº ©´;ô".6ÐúŽ3I$‰=´Z¬‚?y ¤ìe/ ¹µ#W9å [LDû+«?~LÒÂçØ–“ˆVæ¡zS"H¶ ±uí~Orm¯ û×¶JHĈ­IÅÜä Ô&BõkæŽC;Id6kc¤R Ž>Íä”ûeË0Á ú€6“Õ‰-®Áf2õ>Î'Ô?Q}·DûT½ÅAu2‹e;Qh—å¿!,Ä!o^ßh½¦˜ ûCD#j`, ÉfßEÀ©5èÛ–ÀlŒAÛ87­X*ºžc\N/7ÝM;®#H½q‹ex1ýŽUWt]“‡2®Ñª?Rèü•`±»{EwâÛ ³± Î-@|€@v-‹>衆'!Þ¹8ðÁl¾¢FÔñ˜œèrYÑÊ…Dƒ¨DbøˆÅ«½¸­È>þcÃgD®ÖÕÈêãÕxJFžæ0åÝRμ¾ë¡Â§>+ߊÞpR-½ºXL[Ï­ 7êKÿ€üÿÏ|DÙ­ùZØ›VîÉ.%ÇO¸()öUän²«¬GÚWh|°{•á”9dPv¤Ú®Æ5(~Rœ=÷ýNÖ–½ ¬¾DgôÀèÜNjøMw×+ø¾ì¹›ößìiÚáVR÷ •+Ã=³“¸%˜õfxŽþE—@ ðÆ% ÂØ3èÃJܸö|Þ•W{È™ÇÞK63Œn,ÿç}DתY@IëJÄ:\L6˜%• 8‘¶“-J¯§ œ1%){Ã{È£aùA潘Þd{˜Þï¹ÞÅЫ2”¬ö¸¢‹”D±H=IÏS”¢®Iý‚à¦èð'ø\ûký\Æ$g¶øU :í¶ÎÜžÀKêŠq0σç$ùM_u)V X¬þ] z]¯¥\¹LI€+:$Åÿ2E'¸—Õ,&í–D ö’Ž6Vhði?ºÙÇ ½w@,£|ñ— ÷^éßPZ㓦fIR2TgQFk<ÈÕg’&„5×’4xt§zwL€/}ò ã±ôbys[ #޽;ÃL ]óѶ„—CvbE.¨\ÕŸ1b¶ å’Ï€Ú\}3:ý/lŒý14=éÞq78ÈÓÉ[Í÷P8¾yǬ,åê ¥AGËU‘VL¤s5c¬µGÃ5.‚RÁ¸öņ˪.#hþÖ©'iàpUœ$ïøÔ7‰Ž_­ak5•ñýg—ââ÷ú³û±IÃàÓxa«u¿ 89gÍ-ìƒãô÷³›ᘦæù8#1†È‚>ãµÉ6ùÉùPÿmsn1úk²oã¬È7ÛÓÓ0Ôš¨„€bh¥˜Tkç x<¤žVB÷ƒYo’@C—yéŸ&>·MÀºw<{Õï&=.D0g¿DêEU¸Bn÷ÄïéZ:ÓØÑÓ—(­þÀÌd@DšÈR#Ž=zȯļöŸÀ : É•G’"^Úþ‚xÝ<Ôd™·Ö*8E)å¨1–ÐÉyg]zÚ@Œ¼Ž˜T~ ÃÂ1×Ùùà ’ yù~8húãl+úéžâùzÈåT@æÆ:¿o.â˜aç´…¶Ë_we¹]VNÈE kà}¸-¦©J}ÄOu2ÜetY¶ ¬†ø–(o©2äG¸úOIîÐà=õÓS„s Óݱ5­£L\1*Ä$²j‚Ò\F.j(Ùt¸Þ߇Mo,³¦¬*8¿OdP@€7ª™'hïŠn·ŽìÑ‚C*œdã¿ÝÚÚQ«ÊG*àR'ñ%›Ïdq¿„S¯.ûºÞtÆíÈÕ*ÊÍMí\‘ØVEßTSÃIjµº…À…ÁKp¿Ýá6†Ï*°Ì†ž/§ÚΆ¤™SÉ‚ ÖSa©¸÷jÞ4 oKªFcbP’é#:O¢è)ñ»"µ|ë2Î¥âümæö`×ÞçFßÐ1ïâû»Þbiû¢l…z¹#_Á.,¶Ù³ÁoµŸÍâ«Iy ÷­È¾\ç|«Q…&Àƒ #VæRˆeGÄ­•[·c^GØÔìÎ.=ûå®ý¾g/JÐU'…É;Îî¾ÔÞ1C"Òã¤_ÊìÂÉŸ”ŽOé¨ËÐ/ò4&;³µóê ( ´ÃÝÒ`Dò¢5h-.Œ·éY ­0z)E:¾* ø[§P ËXÅôŸVfr0š•(+Í5%ÇY ö,úâ˜ô芎-ÐÅLÍóßär>ǰ‚ó•~B“Û…’¿³£jÉºí£®•Ì©P]’‡á_'g6Òœ0@|Ôbj©Ý… £&3ÑæÖ²¬×ÉBt£I) usHÒ{Îb'^Ê3üv–îoñ”à´Mbz7 He=pišhè;Éç¨Dú/¹*´Æê&Ù”4u$MÖûó9ˤ ùÈŸ«’»yäj)yMCµ¨§ûÛÕëæiï˜øPª'OQâÞ±Àgìpÿ?QUébäìË ð«‹¹”¯n»\1{+!UÞº¾Œ#©BA$R§!9ZGæI&‡Ï^<6pœ†ì.´O9­nzÏŽg01‚”S$*`J2¶õ‚HrÓÄðË_?vØTáöªËÕ­‘Žg 5Œ+Â^ýFÍ'_Ftão`†J‹eÑ‘Ë5u½m{ð#ÌC2¤-å[z¿zRg4`ܱXœÖù½ã@”Sh[º¥­Rd®Ù\Nñ.ó—Íäý׈:=Z,"ôÁWì¡pzv9m …ù?¥†<¥…ôQ¡ê8G‡µñ<ÖóúéMYÖtíœVÑózŸ¦$ݽڈ¸VúC¿Ò2† ?6eŸ†3ìã…ŒMU W~ûsä§OºÝ£')€$ úÙànÒ—¢ƒpòÃK&­åˆ Ç‘[Äÿè; ý¿Ïy §÷jÿD*0REGMtPÜ—zÀ‡"(½úñ˜=Ii‘r¦c¼eKâVöç ïe¬–"‘;垸Ÿ§ƒÈH9ÏâÄLŒì °#(ë–ó1Yk*ÅÓdæ¤1˜ë="%E]Lù±²œÐî·Ú}û„Øà¼±xP&¾iÜ“Úí;‚SeݰÙã}q ^£UN$áäîë•CýùÈ-Ÿ´Ô/½Ú ô‡3­ÍLCq¾V¥.úz.ºI^^éA, òƒº²o±öZ¿ÍØÛ¦ãdR 7€wxÓ”fŠlo^@©4[糪ß$pÏdVŒx¿û†VëË KÌ?QìvãB‘¨äk¡ã µ G³ÄS½M¼Ë° ]d|#¥å¢¥iŒõöÎÓ6lq*ÅùRù3ó¡©ÇxSÿvçä9ÖIã™æ¨?‰Áç­é'úr›&/;¯š·Í7€¤Œ?gÂÖšµ°ãöÓféËKïOöSÒäh‘ö¹PµÇZ7zÄO äõõ$’Z?¥²ô>l ãN ù5ËÖ£ðU¬âí¿@&NIpöšèa,‰^ª`»;:¾I<+¦(lU»upH0X ÙZ¯6»syáÕpû8Îú¡¤&†©A 'JPÀ\Ü–> 0€†<âHâ \$óx%#2­s'lô68õœ¬U©mÑl7gúfØ)óB|U±:sHdR-ÍsvÄ"Í$¸ºE¦³'çÏF˜®œ`-¤ lu‚Ô¢ÙÜjøZ—øAq jwøQkª‰úVFÃܪzÌ íuâŸÞÍ'õƒVv*Ñj‰PuT|ç3­§–xÚÐÈ~õOå›Ê´`„âÍð*7Æ«Û?AÍö–†V6*BR§Y«±ÆX¶XLƒº ï}!àôF õº€ß­Ñ´ÑÛ¿‹‰uq…ºçN9yÔ¨Ç!³)òd“$à$Žò’¹ÛH!w1kÇ7Üu7 ”œ {ŽÙ€^h‹BІ§ee‘´©D¤—÷—. ¿'£€à¾[ýÖ¥4G(·ò(+v3§ûã‚ïe=Bð4[¸½Áܪ³èAÖDä"EJ£,î“õBšð|G˜ÎÝI L™C?ššOÂõ¹Pš§áé>Tå@˜äÊ¿8k6oÚ›JYùÃÉ" ½‹)IQØ‘ÝK;žÅ»Z­Wc—z’ “æÀ…~ ò²Äæ´ŽÐú´)s&¥ ]ÜÈ‹.ÐÈšk¤ÒJð¢·:ñ7ç#ýóâŸé† mJÃÊ“ÙyÖrFyD†fiÄkZ-À&Ú\ ˜„œ€o@½Nå#U~RÀÍvÅ¿çߟú…RˆbŒõuŒœ(í®üŸg@’„¦©ñУB*&±ö9.ÒcÕÈÖTF#!B§bó!:_S—ÿ€©³SðO 9í]9I@Êh?¯J™åÃðyŒ‘Ï좊ž ŒaxH§ÕV …†U€&qhÞÚŠÜã>£Üˆ„ØØ/Z0gê¥BPP¢•ó±=K¤ B6¤ªV³ð«|ÜR¡PŠP6>ó“•ÓC}áW”oA”yÝß…~4³'TÃSÓhÌÝc×i¹d*žSuR0S»þA{¾³¢tü³õfÆišÂ‡üªx ô1b{š™j:ñ:ôì7ª[`Âë—m2B‘§?¶hëû˜ùþ-Z#\â(R¯[ë0 éÐÙ(dJ¤àp׬¤؉TtPˆd-˜òª°É¬ Ã[0“™¸^!™Í¢Pþk%gE;ú°ýÞÆ\BÝÿ0qi’ÈuÉPíID©„”Q~#NdYVð‹ªùÂrtì,ÕXÆ«ö\#U¶1Qá ܺzwàk/ ¦ÀˆðÉÙ¹ ÔÃ24x<Ò[1êÝ*™tbµl‰ªÞ‰YK–çéØ=BX!õ†´øb¶4è” ãHèý÷>ò€¡¬M$+Rg'thŽq¥œDó¬øøYnH•ê­+ö¤r¤çŸQ—éÎCVÐSo'üTÙùÕ«y()ŠJ´ˆƒ”»ƒf–wF›6é˪IŒõ>ÄApð½Å›d°}ZAG¸wVqEy&]fÈâ£^©v7à.7O8_i2…v»‹„“b{¨L–!{/=Ì¢ÎÞ*ŠsÃÅ6› ãˆRl£5$)£¿M+Ùå(A¯a}ÌD©ãבeôM>Ø*$¦€¬™\‰ó £èáÕA!´#0¦_ÁeöLð¶ý ý· ÞB4>AŸÈ/ÿj•c ‹Šš9ª2©Žmv">LP +sôo':7óôrV5€u9 ¯[QϨ$вÄä{ÍÈmKÉTj¥1q‰‹îÕŒ ¸A@ЧcöJ áé-³p~[;KÚfí :׬sDsy¯Õȧ®ì‰LP‚C1siÂMü§Ä‘æ1GŽ’íEl=¥ Eñ“Kñ1º-"-”àÛå0Lï{¹ÁEË6vìl‹ô GÁi.£2lºÈæf È {F¥£ÀѬÇ9’H_ Œ8ëÛËT£ iF¶ ý>9ÛoþË¡î +QåÓDh«„åSPzY‚õ×`ý‰aïæ¤ÕX(þXOy·øÎWày oJNÀjS}ŸT=æF ¸ü,Íó &&«µ¸È2CVPƒÛÃ]ú5Ú áúš¬·VåŠ|gjSW6ïêwäž òÊ‹JÁU=Zò’—%Lׄ^gˆûÙ¥x†¸C£Ô"©=,%J1ÊåÒÊ/V¾c$ÚxMoŃLÑöÖ¤¬¦´)]y}Cðçv¾ò÷%Óh³YúÝM‡iZ$@¾Ó¤Ý7~gè2‹ÂÏ´‡o¿/•ÿ03çR “{Úë·»møç;ˆÂ:XTªjWåæüÁh¹N*¨2³´ g'¯*ЉYÝB¤™Qw·—hw.Fì`òkxG½Zf™Á~zeb³cgÿ;Ug êjÆämjKx‚¬û®jÔ7®Ëg±,ûy¬Ò,뉿L%>ÙàöÚÊê™ [ ù§ÌA bq÷ü$¦Ö3ÙÅóünE öжµ:óìYüƒø¼éiq? Õ¾±(Ü7`TôÍ܈/m«ä3Ýß*N©=̃eQKï&Ë£c±X3bŽ£éb l2œù@øéÛ¨+¡‹>.…ÜΆçUŒ˜û2ø˜'SEÁ×Á ³¾)]!¦ŸnN<ØUSî,³àãyë#wä1·¼¸%óÜÖº=¯æ”!ÇÄ â…þç±6ƒA%í½½´®¥VјÅw½ù&R¼ ìN½¼f7AaÛ§ž[(æ¹ßÊHô"ü‰'w:bªó®W5¦FKPào´¶/Tõ¹‡"ÖZåËîéóqk²“Œ§¦?•ØzÀß’R¡Š›‡ èMÝÔ¸w$qâÞ¦”VÌ&>ÿ}Ùì¹”XŠ0ÏvûÒ×¢ÛIE¤3Z-„»‹ùì­ÆØð“ÕfÛ¹co•º†q³ÆÊ±8GøƒªÍ1âçŒç:˜ah5²RÁÞæSDÜ—°Î »q¨›rÚÝ;¬Ñ‘ÚÂOü3µ‘H=·ùA˜Ï´¹­Ú€á–‡~{S\ì¹i]„ Î1>€px¨å®YVú;¬YxÊ-»]œ)7/gP‚Þíz[ àPÜ•qëmâ• ¯ñØá¡S¢+A D}?‚^h ÖV¿²=Žn~pµ„X%»Ijý‚ŒZ]j¶XÒ`l¿²:í­UfDn뺘ø>GŽü&z{ŠàåÖ˜çЧ‹«·ã˜…¶ÙVL'ŽêtNߣH/ ÆÀÌ|0Éíòc$Oåû³½&zÝn¹WOÓ9вI·Hb–}á @¤[5陼 OÞ—M.¢V+ªD² 4½üõR”{xÃRlcæWcûÞs}çŠN@ÔÝ?÷[B˾' msÔ“ÒhU–I¹™Xà™í«ˆ±3\çFfo/Ét<˜7ñu8ÐæeóÊ÷J}u¹Q‘_uõóÆ)D°áÕƒætøŠcr|~8TôãSíôØâ†BfFîÉD@ƒ ªíöfûýég0‚Tí Êv__ÆH@htk{ŠçÕuß8;Mê³Í펫;zY*=òò†J°™÷Ó—¢âxËGÔo¨GöìŸí×[ûG¿Û±ß·WIóŽTh¯íØÛ­Wö·cn¨òT3ç%ûuöéÝ~¡Ïöê×íÒ¢ øQW8ê™òÇ0"^WéƒÓÑQÑø«¸É‘p#"pYëìñ°¯WwF2?¤yòÂ{wñÚ<–â ÉðÚX*†­¾.!Ѽª{?F< dÁåDCf§2YgnFnD^­î±˜”Nv€šIZî¹í”$ªº¥ ±Ù'†;vK¹Áä±÷Y“á ßõ´Àeü’^[É˜ß ÖZ•(ó&ææ£‰©«bf‰vÉ€I!7,òÛp—>›Ÿgz6“ùÄyWçl¼ˆÇ9¹íë™’øª|zîP—ÛYFÁ²nºZÿ!ñËÁQfr7ògã¦nmÛEµI“˜ð¹5•ƒÂ#2Mž(R,m0q£½–ä¶&» } ¦à+Z•9?®«ý  µ£°QÞâ$Ãú»ç6ï 锇%áÝ5â~%l^Æ ÐhqǘÀûŸßÿ0ê3¬lšåÆãÁUòêŽ÷Ü'¿ÅÀ‰™9]Ý ¼Ø½ÆCÉÃt>AF;.Úèñá ³6àî =(ÍŽ ÞXõ+ „ŸÒ›-  vÑ?` ¦æ\@g +°z˜ ¯nHà+ˆÂ -LïQ_ Q](ÏhVO ç·Ðv׉WüÓ2½WL\í¥€¿„­OH9Íør¬3òÿ„°çë8j¶ÅWœM±ù{é{h´?¤éks²Ô¥;÷‡Të±î–µ}’¦Ü±2/JQnÉ ¸s÷xe7%Æ¡œþ[`L´YƒºO{ý;O«k¨I¹bŸ˜>|3^êûÇÖ¯À,ÝúE§!hËÇHuÊÔ?{w–ì”esŽÀÊ+tQ“ef°­é„yýî)9^hM?E>·[®[¼·€Áiý÷;-ÑÔÚ ]Ì[f,Ɇ‰ ®Âgò“^ûÂTÜÛ­A­X–ÐsZW¸ Ež/¨*šýÛ&…ƒO,‚C(©ßGüËð”&ôÒðý‡ÙZËj‘Î_ÃØš¼jïõ]kU¤#£„ºª8¿`ra3î.W>\÷"JˆÛ$÷½ó§Gs:q{Û,óËj.rJ†‘×ÏÃ̘v(ÄÇGÐk€Ü+Žµç¿ŽIˆ;#ùç‘ ýÌ ð-ƒâ­uÌøËå =t]© PBHf;¡ƒûRæÌy2qižÙ hµg¸[ÅÚo©rûŠóL¥d-ôû¸Ú^‰Ðj=jLÏö4gFÀ@hzÒ'©Øv¶¬¬6A$' s‘¦&%R $â%7#¯ú<`Ã%'×ú½îD;åT7G„Êp»MŽœÇE¡”ßg ÕE«‚ªŽ£WÊï$±UÓÌ—ј‡«e›Øß²HŒú>ádžŠ{ðfkýÁæO:qãZ=8ä{nߊX?þ²Âý$;ž ]ü(59/@Šëì§ûŒí¶eçœ.Ö¥_ôßyÉpG96””ïC)K]`¹Ó½›Š„ý¢æa8G¤ûm©±q1{RѰ«_d[ÕÓÔì8žÏ¿IK¥‚èá±Æ˜Š‘¯É®":½bí&R3õ 5­{Z2`ÓÀ-¼WÈkɧ3xþáá¦Æ}žÒë3b£§w‹$N+²Þ}{ìI…† (ƒ&e¿þsûRâr®™ÿ*îkÑl5\FÃ[•×){>eä9ÛܧýÿEØ-ó‘uÛŒl×âATB¿æ±­îx8?qÙ²–‰ ­uy!lŒB&TÇÄÃ6Ù˜=/9ÆVžôKãˆ4ˆ˜h¾=èTµÉ˜¦s]V?2ZÞ„·–õ57 C&vKÒZfJ¿ ŽËW÷EkÆÔ=nuªLЂ±ñYå«ç’l]©´Þ|ÐïÛfbÞ]OÉ ã)ÜñÁÎ͸..ÕLšÃÀîïDIôˆúÌ÷|¸–ʨŸ>:¾¿Ì©x¦Øí[,e,³OâÝ‘œgôˆ8ql°ûÅ2‹Î²-ŽRÍ`™oªÍTs¾Œ*+ÐÞù7Ѧœ†RCùýtˆîš3ÈUy˜ lò·rŸfÕ„ À=x½“ã¢E$Lð:0Þ'!fo¢Ùå3:9@]QæÎKøVKF·Ú*úŽû’N¹øAÙ­Ó/âØŒjcgvÅ6h¬«™â¦y½ò¥‰ÀÆ…Ž¥»3òÉÞ‘r±‹ÌºL%è±Z/!&Üú/ìÌY¢Ý¾‚¿`=cx3 + úÒ}^kE ZY”…ÆäÀšÊ ÕCÏ»ÇDÌ®),‘(ž˜|A ÍS‘“-·&dÛB@­a‰°º­“9¿Š^—2£–ŠÆÃ _ Ö)NCIwä¢÷P½Â’#ÏJìÍÛX\óÇT«åOÿ!ŽÆk˜Œ1zË’z"ƒçȺ ¨uÛÑÖ˜nÊÕýOm2Ø‚54^ÂânÕ}!mØà(B°,*ßÔ ‹ ÷§ŠàK¥ÅA=„TK»°¥uÆ™×õ”‚`̸l(#xDºåÞQ@Úübz„kCˆëüá-’Δ’EÄÀB¨Y2MÞÃ.o8i©ÖÝÞ¬ußÈ„^‹Q8Ì’®kc N ç D!Ü®fßݺg°O,ü§]…¾c>Ž"š‹¶»5…{ß”§+e0ÈàxǾUâä|î¼»[³ÏDî§Ð‹"é|;üÁõ°PLOŽš@†¹VÑB9,½D9ÑTØ lM¹ÿ?Øp|dÔ’Èq +DO{ƒ•^8Âè;‰œÛË`MàÓhÙ´9ÑltýÕ³ö7ÈŠ‘‰BD3.¯°ÙÙæj3L>TXv$‘S#±Æåª[óZë€ý4aÏÎøPº,Œ•ºž$l=%®ÔÈ»Ícôë‘6V¦À‹ªÿeD:µ#\ ûñÒ4jWš P±©”BÀûz0a××K†ú]LJJ?ŒÁ.€Eâ…‚ÑaˆwæË R)èU"dª*üª~MEÐáO¯—ÞŠ ¡•>¼gfrïüÿDúF:D}ÚTžÂï:û¯ý×3ÙLuC5*³£6Ç:Çß›4å¡7Â)&Iº P÷}>ùsOÑ4 1~ŽVøqΞ•ûõ>½0|Ù[WM ¯ªŒŽi9è‘üRÇÓ¯xË‚ŽLÖIŽ8j³Ï÷èÕ$ÀBÛ"´÷vZ›c€æ»gÎË@7¡âPHÐÇ+Øb˜MGŽ+‚g”ó»Û-˜7ž|¡è•$G°·øt—LO! Þ@¹t}¯‹;ø™.!Wî>¤áÇúthÍâKqxü*Ÿ÷¥þѪIRÃc¾2ÅZ¨¥ÿzûDÑŒ¢òIþ0Ûl¸HSÄŽéþw`ÀbP=ŽNßú.Ÿ^v'léFÙ NÑ'³C7ÅY œéscEؾ÷AÕŒäëV¯B±ÂäÖL§8[fÌrpl?ψØ_Ÿª ÷ùMT-%³ aöWHŸ Ùº7uù<µ§¸@? rFè2ÂÎsÂÀ®jB<±Ø{ßóBÄ{h­÷ÑZ¥ž,{÷ˆ„š1Ã5·|ÌurRª,7HïÇû?õ(] |ý±“šÿHÃîdò:¹_œl6 Q}~œ…numB*‚5k]à¹6ÉävY8y‘Íΰ7¹µð-×obˆä®$4ÈÊßÜDZ‹|Ž­~ÖˆÑÕUˆæ÷”)bÚ{H ìu "k:V½Ñ·{ ñLÂmæfÑ ¹í¥Õ¶ãd”;¥—òD–è3kó‡C J¨€èÙCa+£"Ä x÷ë¶g4Î2~ Ÿò'é}QªÙØŽõÕìÑ\ü.Jm¡$Ò ŒÒÐì2‡ôRù¹ØQ78…Ã~ÕÖú×ÇUÏ9\£?4§È/{Y!7i-:/ªÃrôeÇdç-ú&=÷XSøS~‰ g¸;û‰]ô<=å0±)HLjû¼ËïBx ¸[±Ís¨P¨äÆý8_jÆ2TF· ù€ëÂ/ƒôYZjqŒ‘Ñ…¹¢_š‘& \.«±ŒˆÙÝ H{Ñi«ï0ú™ì&P"åí \»“Ò‰¥×’y©þÎ}â ®¡öÙÍe O¢&JkÄš ˆ:BŽÃf½ÔDÈ•MžÓ뢮 kþøQY£ŽêÎ;8¶%,Iç*Äž:øàdËÕDZh/݃KûZÓ"^¤‰yÓРŪÔܾü¢ï²é.sO4;AŽï®#ÚxfB|ѺnÝ,N¼·çU? w”lÂÇC€7ˆ¶Û,â#Àq4,qœ¦fbɾ76"BÒúêsÆ1¶€J1W;À'é\ß]¯‹"Ó¹T€÷88»ØdªÓÈX—1‚‹K2\vè?a¿_>‹ÙæÓÕ´|ÇOдÇšr²~b°ÀÐÍØ¤ó §@-Öï ¡ÆIuGbQ«Îò·ò …fCóo³î:0„^OñÕ;ú+•pūϭ¹ó˜‰ÌòܬDÐ*ðá^pŽæ¶ÔÒÁ­ûDeÚ™~Œòµ¥@à~s±Ý—h#†zS÷uxù“^ÜRú~IÐ¹Õ r?c‹ÊV°„_û-Ò]=ø·4$ ÌÑ©ÐÊ,]8Ív]S¤H@ÊBŸÒ]0ÉÍØè ¹50æF†õï±gyÓÊØŠÑ€¾ZœµêÍú:«çø–¿Ú|ö~æäгç­@3s ó5»…\Pp}]\Í/Ï®>£‹göÙõ°Ö ~ªÒ4?(Ñ"Œ9Æ!™%EÝLláýn( [$o±Og®Cò¾h  t³Â´q8¶Jdï_·ý?þ%ÏÜtAp4WŒÄDä´vX.ƒé9Í,“é1@èä1Ó¼ÔŸ­KSL–LÉší⯓˜«Ý×¹ªüµJ=èln/KæÇб’BÌœ‹µH:¨€_úãü KK{R»Dzhû•ì†þ?3zŸãÆ|íˆqâ3÷iQà` ÕP@èp²¼{â`¥r»DF„~ɹL’ˆF Œ§*öÝ•xÏ^jV#ù÷ÞE·Ò+EËtxDèÎ)FO½Ì{,C¦ fÔ²Z:›~ELôÚ²Ü{Šºh’˜Õ BÅ»ÔÿL¿r¶WBÕb^ƒç#=¸dÀ½Cr¦ïãŽÎCF|TØ%ÒÅ=u=9IÖ*?¢Àm-С(¬fÍtS`\僧,–’ï"¹Ó6mf¿Bͽœ8èåã©\¸œHQℹƒcM1&KbŒÑ(/Ê~dƒrë¼ÑAEµsÂS‘“lã2@áf¤mäÚ^’"ž²8FqefIæ»éqŸžó‰ÓÀßÏqÒUÉ¿£í0kÏc³xmUå|úÅê‰`Ç£(žLfÃ…„K^"ðl;4Ç¿ÔËA©¬Að‘Ÿmz"Ä4j´»¦Êx½ ì̸h–à#làÌú©E3–ë¢]/‹ñ±®fáò›õk_ÕÚyùã¡¿â7wé¾Ò>ûDï´vûv+øluöîð×ûuïöîVý»#ûv3öŽþÑOÚNÓ}¤ü|ä=öì/ðØkíÛïá¯[öëçíÜMûvCöìWí%Ÿ8ïÚPÐßQ}B´oöêÿvêÇíØ?Û©ÿvêgíØÏöêçíÕŸÚ;}AŸ´ŒøWî4SÊKy9¶Ñ {+C1^;Lucqø^sŠÓ’p“çþlq^ãî!0~˜°s ¥ÃòÙ¿¦ÐV±ˆ¥ ïB9E’×_3übWŠ6ˤ^_õôgìæ¶FæͪU¡¢qÕÅa|pi|p(›¹kdé F§h¹ ˜º(‚´Cš‡Úã}hTøÓcP‡‚\5 Ñ·#\f.Iíšóc£–ýZáép¾åä¡›i¬ìmœ¥Z ›vƒÖýw=éÍ,gB9Lçê&}©0¥h{ Qñgóh ñSw|ì\a8{ÐÒ4M'cYÈ:^¨í§¹¸Š #®J7>c’S$ˆÂñä®C„ô=3@ÒaÆ ¡4ZôÞ° »Ò{Oï/N¾pÃõGœ8 ¨ š1Å:BóñãßkË­°Ð‰%¬™ÂI§óž¸AuŽB_…߇u‚æó¢ â¬Â讜}û0øÄ¢/.­£¼c íØâèå±à¹ýè0ZÁô…ã÷™j„̼ž<ÒßwƒÁ¦åøÒi»T¶”-o»ˆšûíx `‹¿Êœµ{6¢oEÌi®ŸD¼;§¶å ¥qÎ¥´a¯ð}Ò"Æ™ŸC`sÍD…´ÐõÍ#0ì¤À#¸Í`™í’Æ´ÀD}–KÙLšãIÒØà¥êÓäÝa<š—FÊMpyxÔô¢ˆ( óÜÖY¾b7FÙhŠÇN½XË»-‰•ñU2³òe@à [^—N)3°W:¼ÅÅÊÔ”é1t¶³%ù™w9LM¦µÛ-˜)â°c'a•Æk?Æîv솱Š#\˜Ù¦5§’ø^: y€ù5ߪâ¤}ê¾ ´?ïß‹¼m¥IEýx«ØW1éäÆùÃ5ô¶ô¨.CÃ3ßA-O—¦û1]bÕ–¥„z¾ºÿFí‹D”ñëhî¡1­ŽÚ¨úV[’ÿ[Å3Z<·?nUa2]M Òt”îWÚÓ¡1ç¾Å1Œ½AId}Fne€ivœ©{©¬ü@OVáŸI]ûfØ2­¨rÂâœæ’^øÛsihÒÕªã&àÛ<¸kùæ,£Ð]«emAí늲%Ùð|v¥LªŒ Æ€'êéõA] 4¦óD#¯• ÂN³Wê´qOAâ„ðüZí“Ä8^7w­Q™ö‚æÌî^ì¯@œ ú©Xky™–R,‘ò­WžTJýý*žÚÜ.XfЪ oüÔþ*úŒ1OÐx˵ßý¹«–Ç“üD„Ó’ÿ )Ïôü¼5·I‹•)KÒ/Û†º\n˜Ç4úñœÎk©·Â¯ ç€fáÜÕ×5Ãê†úñšûV;£ Êò…ÝŠn—´Ó"{°Z¾N–¥®7ñTiÅ«S¸Íu¯µŽç §A§+?¥iŽëL·¥¯Zùÿ…”¿–f)'|hß? 8ãC6ñˇ݉úìvx=’Í”]{H¼Ôe·“SFéÏp‘W×þÄ©¿n.žKV2 nŸQ—¢ýñ‹çÄæÕw4«úqǧ÷9u]DîC¤^õ|@4ÉL-è.qCisd†ûœž7ó”»p =å3AÉž&K¥  µ\=ø&¡žDJÞ37’@ „ çP 35Ò¨HÈøH9ß0ƒáHÙ¹\_j²¿W+ÝTšH¼Œ>s§·IÕå—IžtâÎøºžŸÁFÌâI{¦€ã¥@¬±ßÛQ˜pý`RÜÒ]d·Ú¯ÔXžÚmrq]¿ãŽä"ª†ºý ÏIÑNxÁ}”9&æ¹\E¤„(¸~µ³µŸZ»®&2z"ídgåYA”>8cÖk(ãB9¸2¸)j€%£`Àø~#Q2Å<ÌÉ4üƈñqZ­? º½°+Ϧ*ÆÎgM§Ic}ÄUf5Ð'M ¿g/w!È©;,‚è›Ó$•]¾Æç) 8t˜~¼Îgå"9x¸bF„[“å©NìüðG)°Ï«Ê4kjlôQä]õÁTd]ÅW‡y5UQ$KM¿'áþm”‘áQäƒíønÑ’YISµ<ÊU´HyLÈ iÿ!'„ b³s_Ù Ê´äÛi ؃R¦‡Ò´‡½ ´4>At¨ÖýC»8RSYkýoÀ~ûØÏ••§üÊžWJ¹<<Ð. 7ñÅÿ§¥1wl“µ2ã£ØcBPºf[ߢ µâF ¯W’!ÞaâÆÓF «µµ…Eêè¨×‡æç;œI–~Ñ¥?ÃåTSç›KR‘ ‹àÖõAa ¯Y-™ÊçaÆÄ©½:g€ƒÒpÃmù÷Tp1 ~VÉ.¢JÕˆiFÚ/ é«—‘ý·ž¹ siÚwpkîwK—â0 €üÝ’eQÂØñ¶µßS$">÷Ë3NÍ]Z#ÍKR†MbbÉP¡ïy¹sŠ|{˜Íã*².~J%ïÑ5 ¼È®Ã¦Ä'CÏ Ý”¦0ý˜!ÓR<õÜ]¥;XÖLí¥²×Søú¾…leªÝt¯‚OdžS€Ÿéëg%¢;GøùÔØè¦OC¾ÎÞR`ƣ׫eÛíqU3Ñ¿ á(wú×ßfUKºS ªNÜ ^4ì“?p‹(^]ãNEFV~‰aU1FšVù`^-ŠÝibJˆ+£îMŸ m»eF ´]¿[XÜTTz J¾fÎNí 1IXÚÔüÓ²ð]Ö V@GYƒÿ}ƒ)?”þ™nèâÉgùxõÀ+é!Š¿J‚rÒ«ßyX<ý–X²’ß±9:†FôÙ”4lÁN×+!"@zs?¥0ãæ xDî!;G4ݘ‘~òlC“ø=^©®NºDyݬÎxçÈÆm³?Ôu  *¾×0B‘‹rXʪŸŠ§ÚЇgýí6Z<â¦uK½ó¨ìy­?v_ =ÙXÊÍÀç§´gA·H'7üfFl¹bïý“ø¡°§kh ÞNä}º]vjipígi±÷"kÃyj­eNÍ êAñ“õb=T”(²°BåEÞg¥ÃŒ]ùänˆÁ–…‹Y8’7ÐylýÚV_ÊaC~äP…'N¹wö‹[çèKñ0´bš'…Ø~¿)âò¤­eÅNÆpWt˜\ùnÝ%ÙQ2»@wž_ñœB+䚦©Ã|Û¦CÏ3üðÞ󧧬«!ìòá¸]²µ×uáú3†4nL ñ8[§6jÒ¦KÄ ¡à®K?ÀgjsÀdO”AcÒ"¾ª¨>zlžò‚Lcè gÇ^óAµÈñzF°Jcb ‡È0W¶"ÎEùBîÝ®> hS1¹† ÍU7®´“°*>„‡V»aûö¦Ît“ ý—2f8ÕŸG ñ’€uÍìì=—ÄG¡ÞpoĶë /^Âé¶É:p©OñE+¯zæ<é®8JܧÇecvrÃл…B =:–Y±“ë‚Ô¶m^/óGªMY)þœªçU^Û÷Ú=Þ37Úº6镽@tñšS×ÇØUøcFZoã Ð!ïs± $:Â%}ÓÂ!v£›´ÞÅðc_äçææJÞ‹ÅÁ2˜>£s‚­Í£9Rtn ØÚĸ‰‰û©^-siEYèž6_P”Ë©©$sñô<êØ †ÂÔ>¹YJÌ-¼–©ÜtA€•[´ÔT®"år8À…"ÞµHÀz÷>І !öä~ù[”֖߮ÀNNrÞ§C/xâmKÞóÏ”ïP ö ìÊð6¬®Ñ–Q¬àI1b(é>7ìuW!À¶¤¦;Æ õ|’*ñ|)p"#‡’ÅFV¸ ÄU–ôU”™A ý¸IóÔ ÊjAÚ¦óÜ’¾¨ŸÏÞ«ÉT ÎÊB$¶#ÿuýÞ0s:‘2ì£!Ìç;‡ßã_Eî¿–¤'²#ž{ilh»‚„E2Üÿ=ê1}½Î¼jG݃>v¤›D˜Ú™£ø“`½ykwI5“Žs ¢±á|ZÔ@¸9Êe“b¹F\q@&?ç'Ã>]» ‚Ôÿ%T®ýEBN1ð©ÛñÈ\{VfÖ1–Ê|hÛÏôßPÌ™BkA¥Á¹ÁÂë/ÿ%`1- £ÿW°rUp½X¬Ø¦©œ EµøáØŸ sâ±w˜!M^¸# °·‡¥»‡¨\_—¯‘Q§€qž{ !!ƒÞsÔ}ï§d$“9À‹ÉN˜ hj®#®¨OøZ}й˼û–“ÿoŽãz;ì5$¤}Ÿú·å0aân@J’eBøw{I»[™´(ƒ6´§Sèøl%˜Æ!u?’ì¯Gïw÷ zÖTÖ‡²Ì.ÙE²lñ$ÐÃÛQ£€#²ûò %?¶CBp«DÜè ðãGÿo'9þomñþöThËcá|sÝ3'“l›xFš«ö+Å7 úE¦$eŽàY¦V§ë Úìúl£Aâ ‹ÃSÐ_?¥Š9Ôn¸#ì_•2½ÉD.41´1±rDƒÇ:ÚyXà 7mïOfþªÃúõŠs„ĉ,y'¶Å¨wc±È@µ]Á #¶ŒOY#­HÕúÜô—âmªÒtçi>âÆ¡¬}ðL»8Æ[ìò†»{ï äaÑÖ ó éYjiGïYçÇ ’ÍS29Ô«ÐÒ¹¨ 7ìqñƾJïUøZç ž€»a-–>õ7µ‘ ˆÔ²)W ™½8ÌöEŸA­`m†ö“¿W¤:×J°í¨º©”h¼ ó•ÛØ@Ne0—79:&ÄÞD·9ºB•¢óí )?5•Kf^A½*( cÂV ŽÜãz°„Vñ9Èq|Pþ¤9P^•«X÷`÷1·Ò#T)ûš¹kOöE³Ó}L?áz5Y +Ψ᱌7ÿbW†¿ ´»>UáÁR‚y7“4äÍFG›“z5%‚½=˜‘<™77=…!ÕÈÛÿUðP”P2.nÑ×-;Îi[”öâÇÑi„îÕ‘žÕçTg2zÅ2ÚÅžw2ýÏMºîÞUY® }sÿu@Êõ· A­µ²Dö ^“G8D‚¢tÑÑ*©tËm­­W˲pd” ž”%J£x]A€C2áÊ ü€v¢ú/BÚtqžóóŠÏúo/}6äð<ýh8V66EE›ng_ÀH×éXqÉ·2óïp=:}0Ek#O3•‚/ ÍXæj®ó\jî|ÛKEJË¿ìè «,Ý!>,ÚP+$E™“==ÑÀPý7¢6Hއþàà²Ì.º„½xht£•î qxÅý­ø‡…°®~ÌþŸqÁþEmV'GMç¶¹úÕ#=‘Ð÷)vº¢K‹QL¦~ÐŽ¯JnÒÓ5<Ëô³I\÷ßÓýÁ>X¤²Ã‡"ÐÀyV¡Wñl‚aBÖׄ¤¬š¾ô<[~é~ºé()>Bܦ÷`¢/^ŒK|Œ1‚6ãÑQ­¯ UÕ]³p×1‹ R~/Öâ[<6nç#ÛjÅ‘ó¯ùò­ÏMA$ÁέÛ{ŽäسØéGzøã2¯Ÿ©£IKž ÷šÍÆŽÄjH)rëÔÏŒuyºa‘ +tš]pM^u•‘~LÔL &>bÖ©-WðÜç? 1} u@ÏqjÂD`uÅ k«¥µ72iÂyxµ„îÏqìA®FŒ{Ï£ibs?ØØF82´/j¹P½†%Ú§ ®ø@d5ôµÿ*”$1©œ†V:¾øÊ6ÍsÏÑ¡õÉɱ‰–Ä6•î0g|÷+ã„ôøí¦)º&g…ÕÎÃÃÑ'dô—Êàˆ¦HÕ«[ ‚csnHÃÜSž曵­-sl¨u×ÅsŒˆ{­ÎÚåÞ½3ØûØé¥l)N6Bâ7ï«}Ë%Áðë<õ óħÉc$w8K~÷‹>$.ÇùOÂé@çòÒk>F‚³Ã™ßœ“NUï躴ÐÄê»ðÚ òg¦ÈV;°èG•Þ’<â»TòÚ'9•ñKJ\‚õõX±pX¥òÅOçJoº°$}_Ä=§Üejîr‡æ|@± „ék lnS€w^r…oŒI(ö ¿VÕ™Ü'æ@‘+Øš…ÄH¥¤âþÆèH¬[ª5:Ü®ÍÝe¬éŠËŒ :?³7§Šß&Âê‹&oí¾KúæLT¥Ç,’q1©O5/ȇS±ìñFB,ÕŸ£æˆþ_ˆ(ˆMÈ °ò/¨¥Ötò9EPq,¾¡n ¼¹®7Ñ%ÛÉÈn›¢¢N„v]ñ—ôbs:Š ©P?s¡v=c¦©ÀŠ´åг'ÂQ§¿ºçê†ýE¡œ>[énKq¦ÿs vé’4htKæE•ÄmÐ׌„»Q‡ƒq–M-07T¢ôº ¤Ï˜'r‰·¡û^ü§uEH\gµ±kÍßoœÂéAÔLÎ÷zqÏzIhÒð®[Ý¢ºæM SÀ¸H)çªL×™H=.gƒK½K K°¹ý}|!jèí®RÉèç‚dè™÷h‚…Ñ=¨0œœvàrÐÃΧ.Z-Œ«ÔýC\lÁ»GD×!vøY\w¬“÷°¤Û§ßéLYÕ*:™ú9ó/uÀ¦FIø˜JW/Õ*lÄ8³“c ýÓ)…øEx1ɬÇSüÄ£˜h‹7ÑK®æ¡.ØçËÍô(V’êKž‘1èH`,Jˆ‘„ðšâ€ÑLÐÿ0èWFInKV6” ÷så^¬ñ®|PXz)‚‡†àçŠq®º\:†ír%½Í`¯EH±¶ èÌðAÞš ¾Ó2_TôTªL [ [¤ÄïŸdÁ ]ñWÔÓÎáÝ”mJÂxÆ3êž¼ëPrþ•Ú¨GQ4™êLщјzÊ6Ö²‰D¦#“3ø”Þ(úÝÝTJüˆª”ÓØ ¹æ¨p4Q&ƒÎ x‘˜P.™òȤ /q>›·cÒ"DƒÅ‘*eGöu'ã]K@xlõ,ÓÀ*²¹¾ï÷é¹ûÎLúrÈé"ÞHÄŽ+úÊKô†¢hR¨‹¢£ÈõæFis ÒW\¤£´íTí“Î’€³$}¶+©}ëäSEÙ€ICÔÕ?¡ZÖ½Mû‰1ül;Ø…>Oƒ¥äúk/AâR"°¹å; \Zþ¡0Q_Z‰t¬SÁý'×wì{™pÍùo‡‘Ž8Üù%<.–'‰ôsýW1ºHj릨7z‚ùy̱C0`'?âl3 v]¨%U¨þoƒÀ;x¥ðd¶¼‚„)ÜbLÌ*„È1’É»>|–Ÿ[Ëžlç;q•VUŠ #pÚÄúcíÛÖæ–Ñ£¾kD]ašâåEf~U§J>}ÈwÖõÜ1R&ãKÌ™j@9#H¥~gÇy Œ‘ Ü6°mª ³Ù†=*&•Y½š#³56KðL½«).ÖÞ®ã»pËF~æ*fÈ ·ý©€Ž– Â3Q çIc½]ÿ$}sä)ƒ$ÝöHÅ›·˜®•(e’"ö⥼sÚèÓO›)ts`Ø!§‡¢¨zËìÌ\§öWøäuOEp¨Óåâ¤]™b‰H²R!‹Á2(µ†'Hp^õ G»­Œt÷ù|ˆ‡AÈ>`µépó3%;‘lÙÂaåµ³¶<©l”ÐÀ:"NëΉ% `Á£æÊÊÓAÅÝ"´†HúãæDÃ6ìe‘ÑhX¿³þ²5w ê™IÆã:£-WX(ÖŽÚ_¼n¯íMßV¥ñ‰LC\ËÅZ,'`Œs!´K Ï—â aHŒ>F¢4ŸXÈ~5¾[|ÊìDã š PhãKšZ†šï‚ ¬²Ep®×Ȧ`J)-YS¤eå»S0qJˆ_FŽ&Z”ÒÚ‡‚$lh¿[ôAàÝYr:ÕMµ}LH Vî~Nñó¡c÷q›S(CýíX±š‡/ZSï )[¶&ç[_–#Û­ mÒKs(®ˆ ‚&-05_ } ì¾×ØfêÚj«Îî`} ›ø]¬;$Æx˜4äKóÇÿ °>€žœK_‘%|æ µrkkˆù»ô;©gdJgQ°1³Ðö‡$bµ;P§E)°Án;˜sØÌš /'rÙ©Ö^ãXÊý÷­›øKÁG‹A9ˆʆ¶ÚTÚ<˜¹”›²¾øxÆ¥¤NJV»¾t»ì!ÁeVÏ^Ê;]Ÿ¤k†wMÔ%F [ò´*Ì«£TaÜEñÛáÒUrh·ß¬®$ò”B?4£Þ6è±dL½ëÒò¬2©§A÷Å‘|t3E3è­ÏM¿4bciUº{õ à0 x•rôúFÝU¶ÀQ_~o—!:…SZæ¦c‰a(T‡¹fä´–-yríXekÍ"£)‡p–­/8û‰ Kšñw„Ž[†òi<Ó y!]ìsM>JYí>µF,Â.ˆâ-sÃOK©¥ðh ¡,˜åì9C^p:CÓ‰Jw””ñ»£iíXlæw@$ÏñöRùË[˹oN[_¾Ì›gž—ˆ‡ó šÔúFò=òи KË뤸 Á²ÓÏÉÉFµDõ—TnJ¿M®õG¹;$åb¨A¾àÝÐð|oó- ,lZ5Ò{ƒ²7¼ÕþVs|ǰ T¨¨œd²|‹«ŸB±WXJÌ ¡ÑÕ1"‚5UU/~w‡ð©2Ö4AÒá;eÑ·­µñ<ï`LLÞïü%«,¡êB!þ…ÍKáØ¬^ÓÄõ&¯¢h€É.ÃÀ´Í:E’#Q¯f(}¾ ݦ3¾Ž§;yÝP’gŒ\Âp‡ê"¾õëÍî;‹ÿ&o0úy»2Öó…laébÈ{a:úðCEԜ٣m ö»µ|‚ým“» K±×M)H,¨„b‚ zÖqµ•cçNИB6.áˆæwš# ËÚkÜ”×ãzäüú(À×ïÔõpƒoîJXoB]äXtâ²%‘ÇåYŽRz¾í HðÔ¨F2e Åj®þß0I´«î•Ú®ý²ñŽR^…[”&q­Áì}ú'TåF}-ÀÔ¯£»,ÿhî…i’\´Óf ð+A’ú!¡â é^Nét¹ä•ï¥aÎ Vç¥Þwê[°2û» ‹̬ô8–²|ö^ sú8$÷]ŒlƒRtîö%4kªDËÌNð,•­`zRßœ<}%m"ì–«7ú±•-™¿ Ù0ÕÓ"|ÐNyÃ9,¹©X4Ýå*$¾ƒ~<£ÅÆÀ¨Þºòù^³ÄDÞÞ<+\ñÃhÂvPFºå»ŽÁ<³ÿ8ô*ÜLpù“tµƒW‹S²q·G JÑ $‹ìç0ã© mG-)ì¤ßrcx”ÿ_ÚwUw=Q+TZ»¸ìs“’Â¥F®U㬠#£Ÿž!ƒèýr¯Â…xë•SñR4¶$E5ÛÅq`{ü“ïÚ¯U[—Š_Ìþ´£ëqOÌ D&R?›í²EûFŸ!vñþÏùs3n·oÃö~ßb0ÁTJqDKa¿Á#Õ¿YhY‘š¶î¡îoÿ?$w×ÏŠBÛe©;± S6N ïÈC€êðѵK#Í{šT&xó@ QÕ¿rz†hÿÈK<Üor­KŽÈÂr‰ôÑÉ RçÌ`i|“(@уq&à¨ä¡C(¸yiœk\Ý]»[]'m“ÜIj£[Q¼Ü«2cì¹?Œuù ž<-Áêh(îú£Š°J}§Mf ‰¡Ýöƒ;¾ƒ)@'ëL±IׯøÅ´Šåû»yÜIÐÜB2—}À8s)õ‚N•M¬æûü̲W2«7¦C¼}S,c>ÚCÞX뽸õmãÕѽ( ½<¯ÃP'âã¦J‘þ/›h¢x%ª…Qq§ÂVÑyìÓ‰î ÝÓ`+x²k̈hPµz+NÈÁÀ‡óùDgaoƒ Ón)DíÐ÷CÖICh4ÒOEc*«øÿ-@ˆ€¼i‰¾+¶#ƒÔŸÚê=qÅíº§‡Ç7Fµ­²€F ÔéêÅ$Ùç$µÃ7†ûD"™Ji'%Ÿ t[ e幌:¥ìA!«ˆ†½Ç=½pÞDgŠU£ ÿ{ìjae±Y.];ÿÙicnV Bðpfstools-2.2.0/src/pfsview/mac_os/Info.plist0000664000701400070140000000072714105165614017552 0ustar rkm38rkm38 CFBundleIconFile pfsview_icon_mac.icns CFBundlePackageType APPL CFBundleGetInfoString pfstools 1.8 CFBundleSignature ttxt CFBundleExecutable pfsview pfstools-2.2.0/src/pfsview/pfsview.qrc0000664000701400070140000000015114105165614016522 0ustar rkm38rkm38 icons/appicon.png pfstools-2.2.0/src/pfsview/histogram.h0000664000701400070140000000313114105165614016477 0ustar rkm38rkm38#ifndef HISTOGRAM_H #define HISTOGRAM_H /** * @brief * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Rafal Mantiuk and Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Rafal Mantiuk, * * $Id: histogram.h,v 1.2 2005/09/02 13:10:35 rafm Exp $ */ #include class Histogram { float *P; int bins; int accuracy; public: Histogram( int bins, int accuracy = 1 ); ~Histogram(); void computeLog( const pfs::Array2D *image ); void computeLog( const pfs::Array2D *image, float min, float max ); int getBins() const { return bins; } float getMaxP() const; float getP( int bin ) const { assert( bin < bins ); return P[bin]; } }; #endif pfstools-2.2.0/src/pfsview/main.cpp0000664000701400070140000006453714105165614016002 0ustar rkm38rkm38/** * @brief * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Rafal Mantiuk and Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Rafal Mantiuk, * * $Id: main.cpp,v 1.19 2014/10/13 09:27:38 rafm Exp $ */ #include #include "main.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pfsview_widget.h" #include "luminancerange_widget.h" #define PROGNAME "pfsview" //QApplication *qApp; PFSViewMainWin::PFSViewMainWin( float window_min, float window_max ): QMainWindow( 0 ) { currentFrame = frameList.end(); QScrollArea *pfsViewArea = new PFSViewWidgetArea( this ); pfsView = (PFSViewWidget*)pfsViewArea->widget(); setCentralWidget( pfsViewArea ); setWindowIcon( QIcon( ":icons/appicon.png" ) ); QAction *nextFrameAct = new QAction( tr( "&Next frame" ), this ); nextFrameAct->setStatusTip( tr( "Load next frame" ) ); nextFrameAct->setShortcut( Qt::Key_PageDown ); connect( nextFrameAct, SIGNAL(triggered()), this, SLOT(gotoNextFrame()) ); QAction *previousFrameAct = new QAction( tr( "&Previous frame" ), this ); previousFrameAct->setStatusTip( tr( "Load previous frame" ) ); previousFrameAct->setShortcut( Qt::Key_PageUp ); connect( previousFrameAct, SIGNAL(triggered()), this, SLOT(gotoPreviousFrame()) ); QToolBar *toolBar = addToolBar( tr( "Navigation" ) ); // toolBar->setHorizontalStretchable( true ); QToolButton *previousFrameBt = new QToolButton( toolBar ); previousFrameBt->setArrowType( Qt::LeftArrow ); previousFrameBt->setMinimumWidth( 15 ); connect( previousFrameBt, SIGNAL(clicked()), this, SLOT(gotoPreviousFrame()) ); previousFrameBt->setToolTip( "Goto previous frame" ); toolBar->addWidget( previousFrameBt ); QToolButton *nextFrameBt = new QToolButton( toolBar ); nextFrameBt->setArrowType( Qt::RightArrow ); nextFrameBt->setMinimumWidth( 15 ); connect( nextFrameBt, SIGNAL(clicked()), this, SLOT(gotoNextFrame()) ); nextFrameBt->setToolTip( "Goto next frame" ); toolBar->addWidget( nextFrameBt ); QLabel *channelSelLabel = new QLabel( "&Channel", toolBar ); channelSelection = new QComboBox( toolBar ); channelSelLabel->setBuddy( channelSelection ); connect( channelSelection, SIGNAL( activated( int ) ), this, SLOT( setChannelSelection(int) ) ); toolBar->addWidget( channelSelLabel ); toolBar->addWidget( channelSelection ); toolBar->addSeparator(); QLabel *mappingMethodLabel = new QLabel( "&Mapping", toolBar ); mappingMethodLabel->setAlignment( Qt::AlignRight | Qt::AlignVCenter ); // | // Qt::TextExpandTabs | Qt::TextShowMnemonic ); mappingMethodCB = new QComboBox( toolBar ); mappingMethodLabel->setBuddy( mappingMethodCB ); mappingMethodCB->addItem( "Linear" ); mappingMethodCB->addItem( "Gamma 1.4" ); mappingMethodCB->addItem( "Gamma 1.8" ); mappingMethodCB->addItem( "Gamma 2.2" ); mappingMethodCB->addItem( "Gamma 2.6" ); mappingMethodCB->addItem( "Logarithmic" ); mappingMethodCB->setCurrentIndex( 3 ); connect( mappingMethodCB, SIGNAL( activated( int ) ), this, SLOT( setLumMappingMethod(int) ) ); toolBar->addWidget( mappingMethodLabel ); toolBar->addWidget( mappingMethodCB ); // addToolBar( Qt::BottomToolBarArea, toolBar ); QToolBar *toolBarLR = addToolBar( tr( "Histogram" ) ); lumRange = new LuminanceRangeWidget( toolBarLR ); connect( lumRange, SIGNAL( updateRangeWindow() ), this, SLOT( updateRangeWindow() ) ); toolBarLR->addWidget( lumRange ); // addToolBar( toolBar ); pointerPosAndVal = new QLabel( statusBar() ); statusBar()->addWidget( pointerPosAndVal ); // QFont fixedFont = QFont::defaultFont(); // fixedFont.setFixedPitch( true ); // pointerPosAndVal->setFont( fixedFont ); zoomValue = new QLabel( statusBar() ); statusBar()->addWidget( zoomValue ); exposureValue = new QLabel( statusBar() ); statusBar()->addWidget( exposureValue ); connect( pfsView, SIGNAL(updatePointerValue()), this, SLOT(updatePointerValue()) ); QMenu *frameMenu = menuBar()->addMenu( tr( "&Frame" ) ); frameMenu->addAction( nextFrameAct ); frameMenu->addAction( previousFrameAct ); frameMenu->addSeparator(); frameMenu->addAction( "&Save image...", this, SLOT(saveImage()), QKeySequence::Save ); frameMenu->addAction( "&Copy image to clipboard", this, SLOT(copyImage()), QKeySequence::Copy ); frameMenu->addSeparator(); frameMenu->addAction( "&Quit", qApp, SLOT(quit()), Qt::Key_Q ); //QKeySequence::Quit QShortcut *shortcut = new QShortcut( QKeySequence::Close, this ); connect( shortcut, SIGNAL(activated()), qApp, SLOT(quit()) ); QAction *act; QMenu *viewMenu = menuBar()->addMenu( tr( "&View" ) ); act = viewMenu->addAction( "&Zoom in", pfsView, SLOT(zoomIn()), Qt::Key_Period ); // QKeySequence::ZoomIn -- not doing it -- silly binding under Linux connect( act, SIGNAL(triggered()), this, SLOT(updateZoomValue()) ); act = viewMenu->addAction( "Zoom &out", pfsView, SLOT(zoomOut()), Qt::Key_Comma ); connect( act, SIGNAL(triggered()), this, SLOT(updateZoomValue()) ); act = viewMenu->addAction( "Zoom &1:1", pfsView, SLOT(zoomOriginal()), Qt::Key_N ); connect( act, SIGNAL(triggered()), this, SLOT(updateZoomValue()) ); viewMenu->addAction( "&Fit window to content", this, SLOT(updateViewSize()), Qt::Key_C ); viewMenu->addSeparator(); QMenu *infnanMenu = viewMenu->addMenu( "NaN and &Inf values" ); QActionGroup *infnanActGrp = new QActionGroup( this ); infnanActGrp->setExclusive( true ); QAction *infnanHideAct = new QAction( tr( "&Hide" ), this ); infnanHideAct->setCheckable(true); infnanHideAct->setData(0); infnanActGrp->addAction( infnanHideAct ); infnanMenu->addAction( infnanHideAct ); QAction *infnanMarkAct = new QAction( tr( "Mark with &red color" ), this ); infnanMarkAct->setCheckable(true); infnanMarkAct->setData(1); infnanActGrp->addAction( infnanMarkAct ); infnanMenu->addAction( infnanMarkAct ); infnanMarkAct->setChecked( true ); connect( infnanActGrp, SIGNAL(triggered(QAction*)), pfsView, SLOT(setInfNaNTreatment(QAction*)) ); QMenu *colorClipMenu = viewMenu->addMenu( "&Color clipping" ); QActionGroup *colorClipActGrp = new QActionGroup( this ); colorClipActGrp->setExclusive( true ); QAction *colorClipSimpleAct = new QAction( tr( "&Simple clipping" ), this ); colorClipSimpleAct->setCheckable(true); colorClipSimpleAct->setData(CLIP_SIMPLE); colorClipSimpleAct->setShortcut( Qt::CTRL + Qt::Key_H ); colorClipActGrp->addAction( colorClipSimpleAct ); colorClipMenu->addAction( colorClipSimpleAct ); QAction *colorClipCodedAct = new QAction( tr( "&Color-coded clipping" ), this ); colorClipCodedAct->setCheckable(true); colorClipCodedAct->setShortcut( Qt::CTRL + Qt::Key_J ); colorClipCodedAct->setData(CLIP_COLORCODED); colorClipActGrp->addAction( colorClipCodedAct ); colorClipMenu->addAction( colorClipCodedAct ); QAction *colorClipBriHueAct = new QAction( tr( "&Keep brightness and hue" ), this ); colorClipBriHueAct->setCheckable(true); colorClipBriHueAct->setShortcut( Qt::CTRL + Qt::Key_K ); colorClipBriHueAct->setData(CLIP_KEEP_BRI_HUE); colorClipActGrp->addAction( colorClipBriHueAct ); colorClipMenu->addAction( colorClipBriHueAct ); colorClipSimpleAct->setChecked( true ); connect( colorClipActGrp, SIGNAL(triggered(QAction*)), pfsView, SLOT(setRGBClippingMethod(QAction*)) ); QMenu *negativeMenu = viewMenu->addMenu( "&Negative values" ); QActionGroup *negativeActGrp = new QActionGroup( this ); negativeActGrp->setExclusive( true ); act = new QAction( tr( "&Black" ), this ); act->setCheckable(true); act->setData(NEGATIVE_BLACK); act->setShortcut( Qt::ALT + Qt::Key_B ); negativeActGrp->addAction( act ); negativeMenu->addAction( act ); act->setChecked( true ); act = new QAction( tr( "Mark with &red color" ), this ); act->setCheckable(true); act->setData(NEGATIVE_MARK_AS_RED); act->setShortcut( Qt::ALT + Qt::Key_R ); negativeActGrp->addAction( act ); negativeMenu->addAction( act ); act = new QAction( tr( "Use &green color scale" ), this ); act->setCheckable(true); act->setData(NEGATIVE_GREEN_SCALE); act->setShortcut( Qt::ALT + Qt::Key_G ); negativeActGrp->addAction( act ); negativeMenu->addAction( act ); act = new QAction( tr( "Use &absolute values" ), this ); act->setCheckable(true); act->setData(NEGATIVE_ABSOLUTE); act->setShortcut( Qt::ALT + Qt::Key_A ); negativeActGrp->addAction( act ); negativeMenu->addAction( act ); connect( negativeActGrp, SIGNAL(triggered(QAction*)), pfsView, SLOT(setNegativeTreatment(QAction*)) ); viewMenu->addSeparator(); QMenu *colorCoordMenu = viewMenu->addMenu( "Color coo&rdinates" ); QActionGroup *colorCoordActGrp = new QActionGroup( this ); colorCoordActGrp->setExclusive( true ); act = new QAction( tr( "&RGB" ), this ); act->setCheckable(true); act->setData(CC_RGB); act->setShortcut( Qt::SHIFT + Qt::ALT + Qt::Key_R ); colorCoordActGrp->addAction( act ); colorCoordMenu->addAction( act ); act->setChecked( true ); act = new QAction( tr( "&XYZ" ), this ); act->setCheckable(true); act->setData(CC_XYZ); act->setShortcut( Qt::SHIFT + Qt::ALT + Qt::Key_X ); colorCoordActGrp->addAction( act ); colorCoordMenu->addAction( act ); act = new QAction( tr( "Y&u'v'" ), this ); act->setCheckable(true); act->setData(CC_Yupvp); act->setShortcut( Qt::SHIFT + Qt::ALT + Qt::Key_U ); colorCoordActGrp->addAction( act ); colorCoordMenu->addAction( act ); act = new QAction( tr( "Yx&y" ), this ); act->setCheckable(true); act->setData(CC_Yxy); act->setShortcut( Qt::SHIFT + Qt::ALT + Qt::Key_Y ); colorCoordActGrp->addAction( act ); colorCoordMenu->addAction( act ); connect( colorCoordActGrp, SIGNAL(triggered(QAction*)), this, SLOT(setColorCoord(QAction*)) ); QMenu *mappingMenu = menuBar()->addMenu( tr( "&Tone mapping" ) ); mappingMenu->addAction( "Increase exposure", lumRange, SLOT(increaseExposure()), Qt::Key_Minus ); mappingMenu->addAction( "Decrease exposure", lumRange, SLOT(decreaseExposure()), Qt::Key_Equal ); mappingMenu->addAction( "Extend dynamic range", lumRange, SLOT(extendRange()), Qt::Key_BracketRight ); mappingMenu->addAction( "Shrink dynamic range", lumRange, SLOT(shrinkRange()), Qt::Key_BracketLeft ); mappingMenu->addAction( "Fit to dynamic range", lumRange, SLOT(fitToDynamicRange()), Qt::Key_Backslash ); mappingMenu->addAction( "Low dynamic range", lumRange, SLOT(lowDynamicRange()), Qt::ALT + Qt::Key_L ); QMenu *mapfuncMenu = mappingMenu->addMenu( "&Mapping function" ); QActionGroup *mapfuncActGrp = new QActionGroup( this ); mapfuncActGrp->setExclusive( true ); mappingAct[0] = act = new QAction( tr( "&Linear" ), this ); act->setCheckable(true); act->setData(0); act->setShortcut( Qt::Key_L ); mapfuncActGrp->addAction( act ); mapfuncMenu->addAction( act ); mappingAct[1] = act = new QAction( tr( "Gamma 1.&4" ), this ); act->setCheckable(true); act->setData(1); act->setShortcut( Qt::Key_1 ); mapfuncActGrp->addAction( act ); mapfuncMenu->addAction( act ); mappingAct[2] = act = new QAction( tr( "Gamma 1.&8" ), this ); act->setCheckable(true); act->setData(2); act->setShortcut( Qt::Key_2 ); mapfuncActGrp->addAction( act ); mapfuncMenu->addAction( act ); mappingAct[3] = act = new QAction( tr( "Gamma 2.&2" ), this ); act->setCheckable(true); act->setData(3); act->setChecked( true ); act->setShortcut( Qt::Key_3 ); mapfuncActGrp->addAction( act ); mapfuncMenu->addAction( act ); mappingAct[4] = act = new QAction( tr( "Gamma 2.&6" ), this ); act->setCheckable(true); act->setData(4); act->setShortcut( Qt::Key_4 ); mapfuncActGrp->addAction( act ); mapfuncMenu->addAction( act ); mappingAct[5] = act = new QAction( tr( "L&ogarithmic" ), this ); act->setCheckable(true); act->setData(5); act->setShortcut( Qt::Key_O ); mapfuncActGrp->addAction( act ); mapfuncMenu->addAction( act ); connect( mapfuncActGrp, SIGNAL(triggered(QAction*)), this, SLOT(setLumMappingMethod(QAction*)) ); QMenu *helpMenu = menuBar()->addMenu( tr( "&Help" ) ); helpMenu->addAction( "&About", this, SLOT(showAboutdialog()) ); colorCoord = CC_RGB; //Window should not be larger than desktop // TODO: how to find desktop size - gnome taksbars // setMaximumSize( QApplication::desktop()->width(), QApplication::desktop()->height() ); try { if( !readNextFrame() ) throw PFSViewException(); if( window_min < window_max ) lumRange->setRangeWindowMinMax( window_min, window_max ); } catch( pfs::Exception ex ) { QMessageBox::critical( this, "pfsview error", ex.getMessage() ); throw PFSViewException(); } } /* void PFSViewMainWin::changeClippingMethod( int clippingMethod ) { for( int i = 0; i < CLIP_COUNT; i++ ) viewMenu->setItemChecked( clippingMethodMI[i], clippingMethod == i ); statusBar()->message( viewMenu->text(clippingMethodMI[clippingMethod]), 1000 ); } void PFSViewMainWin::setInfNaNTreatment( int method ) { infnanMenu->setItemChecked( infNaNMI[0], method == 0 ); infnanMenu->setItemChecked( infNaNMI[1], method == 1 ); } void PFSViewMainWin::setNegativeTreatment( int method ) { for( int i = 0; i < NEGATIVE_COUNT; i++ ) negativeMenu->setItemChecked( negativeMI[i], method == i ); } */ void PFSViewMainWin::setLumMappingMethod( int method ) { mappingMethodCB->setCurrentIndex( method ); pfsView->setLumMappingMethod( method ); mappingAct[method]->setChecked( true ); } void PFSViewMainWin::setLumMappingMethod( QAction *action ) { int method = action->data().toUInt(); mappingMethodCB->setCurrentIndex( method ); pfsView->setLumMappingMethod( method ); } struct ColorSpaceLabel { const char *comboBoxLabel; const char *c1, *c2, *c3; }; static const ColorSpaceLabel scLabels[] = { { "Color XYZ", "X", "Y", "Z" } }; int scLabelsCount = sizeof( scLabels ) / sizeof( ColorSpaceLabel ); void PFSViewMainWin::updatePointerValue() { const PointerValue &pv = pfsView->getPointerValue(); if( pv.valid ) { char pointerValueStr[256]; const PointerValue &pv = pfsView->getPointerValue(); sprintf( pointerValueStr, "(x,y)=(%4d,%4d) ", pv.x, pv.y ); QString label( pointerValueStr ); int channelSel = channelSelection->currentIndex(); if( pv.valuesUsed == 3 ) { // Color assert( channelSel < scLabelsCount ); pfs::Array2DImpl X(1,1), Y(1,1), Z(1,1); X(0) = pv.value[0]; Y(0) = pv.value[1]; Z(0) = pv.value[2]; const char *l1, *l2, *l3; switch( colorCoord ) { case CC_RGB: l1 = "R"; l2 = "G"; l3 = "B"; pfs::transformColorSpace( pfs::CS_XYZ, &X, &Y, &Z, pfs::CS_RGB, &X, &Y, &Z ); break; case CC_XYZ: l1 = "X"; l2 = "Y"; l3 = "Z"; break; case CC_Yupvp: pfs::transformColorSpace( pfs::CS_XYZ, &X, &Y, &Z, pfs::CS_YUV, &X, &Y, &Z ); l1 = "Y"; l2 = "u'"; l3 = "v'"; break; case CC_Yxy: pfs::transformColorSpace( pfs::CS_XYZ, &X, &Y, &Z, pfs::CS_Yxy, &X, &Y, &Z ); l1 = "Y"; l2 = "x"; l3 = "y"; break; case CC_COUNT: assert( 0 ); break; }; sprintf( pointerValueStr, "%s=%07.4g %s=%07.4g %s=%07.4g", l1, X(0), l2, Y(0), l3, Z(0) ); label += pointerValueStr; lumRange->showValuePointer( log10(pv.value[1]) ); } else { // Single channel const QString &name = channelSelection->itemText( channelSel ); sprintf( pointerValueStr, "%s=%07.4g", (const char*)name.toLatin1(), pv.value[0] ); label += pointerValueStr; lumRange->showValuePointer( log10(pv.value[0]) ); } pointerPosAndVal->setText( label ); } else { pointerPosAndVal->setText( "(?,?)" ); lumRange->hideValuePointer(); } } void PFSViewMainWin::setColorCoord( QAction *action ) { colorCoord = (ColorCoord)action->data().toUInt(); updatePointerValue(); } void PFSViewMainWin::updateZoomValue() { char strBuf[20]; sprintf( strBuf, "%d%%", (int)(pfsView->getZoom()*100) ); zoomValue->setText( strBuf ); } void PFSViewMainWin::updateViewSize() { // QSize sz = sizeHint(); // printf( "main window: %d %d\n", sz.width(), sz.height() ); centralWidget()->updateGeometry(); resize( sizeHint() ); } void PFSViewMainWin::updateRangeWindow() { pfsView->setRangeWindow( pow( 10, lumRange->getRangeWindowMin() ), pow( 10, lumRange->getRangeWindowMax() ) ); char ev_str[100]; sprintf( ev_str, "EV = %+.2g f-stops %+.2g D %.4g", -lumRange->getRangeWindowMax()*log(2)/log(10), -lumRange->getRangeWindowMax(), 1/pow( 10, lumRange->getRangeWindowMax() ) ); exposureValue->setText( ev_str ); } void PFSViewMainWin::updateChannelSelection() { channelSelection->clear(); const char *visibleChannel = pfsView->getVisibleChannel(); if( pfsView->hasColorChannels() ) { channelSelection->addItem( "Color XYZ" ); if( !strcmp( visibleChannel, "Color" ) ) channelSelection->setCurrentIndex( 0 ); } /* channelSelection->insertItem( "Color RGB" ); channelSelection->insertItem( "Color XYZ" ); channelSelection->insertItem( "Color L*ab" ); channelSelection->insertItem( "Luminance Y" );*/ QList channelNames = pfsView->getChannels(); for( int c = 0; c < channelNames.size(); c++ ) { channelSelection->addItem( channelNames[c] ); if( !strcmp( channelNames[c], visibleChannel ) ) channelSelection->setCurrentIndex( channelSelection->count()-1 ); } } void PFSViewMainWin::setChannelSelection( int selectedChannel ) { // If channel combo box has not been initialized yet if( selectedChannel >= channelSelection->count() ) return; const QString &name = channelSelection->itemText( selectedChannel ); if( pfsView->hasColorChannels() && selectedChannel < scLabelsCount ) { pfsView->setVisibleChannel( NULL ); // Color } else { pfsView->setVisibleChannel( (const char*)name.toLatin1() ); } updateMenuEnable( ); lumRange->setHistogramImage(pfsView->getPrimaryChannel()); updatePointerValue(); } void PFSViewMainWin::updateMenuEnable( ) { bool color = pfsView->getVisibleChannel() == COLOR_CHANNELS; // negativeMenu->setItemEnabled( negativeMI[NEGATIVE_GREEN_SCALE], !color ); // negativeMenu->setItemEnabled( negativeMI[NEGATIVE_ABSOLUTE], !color ); } void PFSViewMainWin::showAboutdialog() { QMessageBox::information( this, "pfsview", "pfsview " PACKAGE_VERSION "\n" "Copyright (C) 2005-2011 Rafal Mantiuk\n\n" "See the manual page (man pfsview) and\n" "the web page (http://pfstools.sourceforge.net/)\n" "for more information" ); } // ======================= Loading next frame ========================== /** * GUI action -> goto next frame * handle error and eof of frame */ void PFSViewMainWin::gotoNextFrame() { try { if( !readNextFrame() ) { statusBar()->showMessage( "No more frames", 1000 ); } } catch( pfs::Exception ex ) { // Display message and keep the old frame QMessageBox::critical( this, "pfsview error", ex.getMessage() ); qApp->quit(); } } void PFSViewMainWin::gotoPreviousFrame() { currentFrame++; if( currentFrame == frameList.end() ) { currentFrame--; statusBar()->showMessage( "No more frames in buffer (buffer holds max 5 frames)", 1000 ); return; } setFrame( *currentFrame ); } void PFSViewMainWin::setFrame( pfs::Frame *frame ) { QApplication::setOverrideCursor( QCursor( Qt::WaitCursor ) ); pfsView->setFrame( frame ); lumRange->setHistogramImage(pfsView->getPrimaryChannel()); updateChannelSelection(); updateZoomValue(); const char *luminanceTag = frame->getTags()->getString( "LUMINANCE" ); if( luminanceTag != NULL && !strcmp( luminanceTag, "DISPLAY" ) ) { setLumMappingMethod(0); lumRange->lowDynamicRange(); } updateRangeWindow(); // Set the window caption to the name of the frame, if it is given const char *fileName = frame->getTags()->getString( "FILE_NAME" ); QString qLuminance( "" ); if( luminanceTag!=NULL ) qLuminance=QString(" (") + QString(luminanceTag) + QString(")"); if( fileName == NULL ) setWindowTitle( QString( "pfsview" ) ); else { QString qFileName( fileName ); if( qFileName.length() > 30 ) setWindowTitle( QString( "pfsview: ... " ) + QString( fileName ).right( 30 ) + qLuminance); else setWindowTitle( QString( "pfsview: " ) + QString( fileName ) + qLuminance); } updateMenuEnable( ); QApplication::restoreOverrideCursor(); } /** * Load next frame from the stream. * @return false if there are no more frames */ bool PFSViewMainWin::readNextFrame() { if( currentFrame != frameList.begin() ) { currentFrame--; setFrame( *currentFrame ); return true; } pfs::DOMIO pfsCtx; pfs::Frame *newFrame; newFrame = pfsCtx.readFrame( stdin ); if( newFrame == NULL ) return false; // No more frames if( frameList.size() == MAX_FRAMES_IN_MEMORY ) { // Remove one frame from the back pfsCtx.freeFrame( frameList.back() ); frameList.pop_back(); } frameList.push_front( newFrame ); currentFrame = frameList.begin(); setFrame( newFrame ); return true; } // ======================= Saving image ================================ void PFSViewMainWin::saveImage() { QString fileName = QFileDialog::getSaveFileName( this, "Save current view as...", "./", "*.png; *.jpg" ); QByteArray jpeg_format( "jpg" ); QByteArray png_format( "png" ); if ( !fileName.isNull() ) { // got a file name QList file_formats = QImageWriter::supportedImageFormats(); QList::iterator it; QByteArray *format = NULL; for( it = file_formats.begin(); it != file_formats.end(); it++ ) { if( fileName.endsWith( (const char*)*it ) ) { format = &(*it); break; } } if( format == NULL ) { int sel = QMessageBox::question( this, "Select image format", "The file name is missing extension or the extension is not recongnized as an image format. Choose the image format.", "Cancel", "JPEG image", "PNG image" ); if( sel == 1 ) { format = &jpeg_format; fileName = fileName + ".jpg"; } else if( sel == 2 ) { format = &png_format; fileName = fileName + ".png"; } } if( format != NULL ) { QImage *image = pfsView->getDisplayedImage(); if( !image->save( fileName, format->data(), -1 ) ) { QMessageBox::warning( this, "Saving image problem", "Cannot save image " + fileName ); } else statusBar()->showMessage( fileName + QString( "' saved as " ) + QString( format->data() ) + QString( " image" ), 4000 ); } } } void PFSViewMainWin::copyImage() { QClipboard *cb = QApplication::clipboard(); const QImage *image = pfsView->getDisplayedImage(); cb->setImage( *image, QClipboard::Clipboard ); statusBar()->showMessage( "Image copied to clipboard", 2000 ); } // ======================= main and command line ======================= void printUsage() { fprintf( stderr, "Usage: " PROGNAME " [options]\n" "\n" "Displays high-dynamic range image in pfs format. The image is read from the " "standard output.\n" "\n" "options:\n" "\t--help : prints this message\n" "\t--window_min log_lum : lower bound of hdr display window, given as " "a log10 of luminance.\n" "\t--window_min log_lum : the same as above, but the upper bound\n" ); } static void errorCheck( bool condition, const char *string ) { if( !condition ) { fprintf( stderr, PROGNAME " error: %s\n", string ); abort(); } } int main(int argc, char** argv) { QApplication app(argc, argv); // qApp = &app; float window_min = 0, window_max = 0; for( int i=1 ; i -100, "--window_min expects floating point value between -100 and 100" ); } else if( !strcmp( argv[i], "--window_max") ) { i++; errorCheck( i < argc, "expected parameter after --window_max" ); char *checkPtr; window_max = (float)strtod( argv[i], &checkPtr ); errorCheck( checkPtr != NULL && checkPtr[0] == 0 && window_max < 100 && window_max > -100, "--window_max expects floating point value between -100 and 100" ); } else { fprintf( stderr, PROGNAME " error: not recognized parameter '%s'\n", argv[i] ); printUsage(); return -1; } } errorCheck( window_min <= window_max, "window_min must be less than window_max" ); try { PFSViewMainWin pfsViewMW( window_min, window_max ); // app.setMainWidget(&pfsViewMW); pfsViewMW.show(); pfsViewMW.updateViewSize(); return app.exec(); } catch( PFSViewException ex ) { QMessageBox::critical( NULL, "pfsview error", "Can not open the first PFS frame. Quitting." ); return -1; } } pfstools-2.2.0/src/pfsview/CMakeLists.txt0000664000701400070140000000266614105165614017105 0ustar rkm38rkm38include_directories ("${PROJECT_BINARY_DIR}/" "${PROJECT_SOURCE_DIR}/src/pfs" ${Qt5Widgets_INCLUDE_DIRS}) if( NOT HAS_GETOPT ) include_directories ("${GETOPT_INCLUDE}") endif( NOT HAS_GETOPT ) #include_directories ("${PROJECT_BINARY_DIR}/" "${PROJECT_SOURCE_DIR}/src/pfs" ${Qt5Widgets_INCLUDE_DIRS}) link_directories("${PROJECT_SOURCE_DIR}/src/pfs") SET(pfsview_SOURCES main.cpp pfsview_widget.cpp luminancerange_widget.cpp histogram.cpp resources.cpp) SET(pfsview_HEADERS main.h pfsview_widget.h luminancerange_widget.h) if (OPENMP_FOUND) # set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}") set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}") endif() QT5_WRAP_CPP(pfsview_HEADERS_MOC ${pfsview_HEADERS}) QT5_WRAP_CPP(pfsview_HEADERS_MOC ${pfsview_HEADERS}) # Replace the tag with the path to bash file(READ ${CMAKE_CURRENT_SOURCE_DIR}/pfsv.in file_content) string(REGEX REPLACE "@BASH_PATH@" "${BASH_EXECUTABLE}" file_content "${file_content}") file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/pfsv" "${file_content}") install (FILES "${CMAKE_CURRENT_BINARY_DIR}/pfsv" PERMISSIONS OWNER_EXECUTE GROUP_EXECUTE WORLD_EXECUTE OWNER_WRITE WORLD_READ GROUP_READ OWNER_READ DESTINATION bin) ADD_EXECUTABLE(pfsview ${pfsview_SOURCES} ${pfsview_HEADERS_MOC} "${GETOPT_OBJECT}") TARGET_LINK_LIBRARIES(pfsview pfs Qt5::Widgets) install (TARGETS pfsview DESTINATION bin) install (FILES pfsview.1 DESTINATION ${MAN_DIR}) pfstools-2.2.0/src/pfsview/histogram.cpp0000664000701400070140000000502414105165614017035 0ustar rkm38rkm38/** * @brief * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Rafal Mantiuk and Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Rafal Mantiuk, * * $Id: histogram.cpp,v 1.2 2005/09/02 13:10:35 rafm Exp $ */ #include #include #include #include "histogram.h" Histogram::Histogram( int bins, int accuracy ): bins( bins ), accuracy( accuracy ) { P = new float[bins]; } Histogram::~Histogram() { delete[] P; } void Histogram::computeLog( const pfs::Array2D *image ) { const int size = image->getRows()*image->getCols(); float max, min; // Find min, max { min = 999999999; max = -999999999; for( int i = 0; i < size; i += accuracy ) { float v = (*image)(i); if( v > max ) max = v; else if( v < min ) min = v; } } computeLog( image, min, max ); } void Histogram::computeLog( const pfs::Array2D *image, float min, float max ) { const int size = image->getRows()*image->getCols(); // Empty all bins for( int i = 0; i < bins; i++ ) P[i] = 0; float count = 0; float binWidth = (max-min)/(float)bins; for( int i = 0; i < size; i += accuracy ) { float v = (*image)(i); if( v <= 0 ) continue; v = log10(v); int bin = (int)((v-min)/binWidth); if( bin > bins || bin < 0 ) continue; if( bin == bins ) bin = bins-1; P[bin] += 1; count++; } // Normalize, to get probability for( int i = 0; i < bins; i++ ) P[i] /= (float)(count/accuracy); } float Histogram::getMaxP() const { float maxP = -1; for( int i = 0; i < bins; i++ ) { if( P[i] > maxP ) { maxP = P[i]; } } return maxP; } pfstools-2.2.0/src/pfsview/pfsv.10000664000701400070140000000115414105165614015374 0ustar rkm38rkm38.TH "pfsv" 1 .SH NAME pfsv \- Viewer for high-dynamic range images .SH SYNOPSIS .B pfsv [files...] .SH DESCRIPTION Display a HDR image or sequence of images using pfsview. This is a shell wrapper that invokes pfsin to load an image in one of the several recognized formats (see pfsin for the list of formats) and displays it using pfsview. Note: This program was renamed from \fBpv\fR to avoid conflict with the pipe viewer 'pv'. .SH EXAMPLES .TP pfsv memorial.hdr Display memorial image. .SH "SEE ALSO" .BR pfsview (1) .BR pfsin (1) .SH BUGS Please report bugs and comments to Rafal Mantiuk . pfstools-2.2.0/src/pfsview/luminancerange_widget.h0000664000701400070140000000514514105165615021045 0ustar rkm38rkm38#ifndef LUMINANCERANGE_WIDGET_H #define LUMINANCERANGE_WIDGET_H /** * @brief * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Rafal Mantiuk and Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Rafal Mantiuk, * * $Id: luminancerange_widget.h,v 1.4 2013/12/21 19:42:29 rafm Exp $ */ #include #include #include class Histogram; namespace pfs { class Array2D; } class LuminanceRangeWidget : public QFrame { Q_OBJECT public: LuminanceRangeWidget( QWidget *parent=0 ); ~LuminanceRangeWidget(); QSize sizeHint () const; protected: void paintEvent( QPaintEvent * ); void mouseMoveEvent( QMouseEvent * ); void mousePressEvent( QMouseEvent * me ); void mouseReleaseEvent(QMouseEvent *); float draggedMin(); float draggedMax(); signals: void updateRangeWindow(); public slots: void decreaseExposure(); void increaseExposure(); void extendRange(); void shrinkRange(); void fitToDynamicRange(); void lowDynamicRange(); private: float minValue; float maxValue; float windowMin; float windowMax; static const int DRAGNOTSTARTED = -1; int mouseDragStart; float dragShift; enum DragMode { DRAG_MIN, DRAG_MAX, DRAG_MINMAX, DRAG_NO }; DragMode dragMode; float valuePointer; Histogram *histogram; const pfs::Array2D *histogramImage; bool showVP; QRect getPaintRect() const; public: float getRangeWindowMin() const { return windowMin; } float getRangeWindowMax() const { return windowMax; } void setRangeWindowMinMax( float min, float max ); void setHistogramImage( const pfs::Array2D *image ); void showValuePointer( float value ); void hideValuePointer(); }; #endif pfstools-2.2.0/src/pfsview/resources.cpp0000664000701400070140000002453414105165615017062 0ustar rkm38rkm38/**************************************************************************** ** Resource object code ** ** Created: Mon Mar 21 14:25:10 2011 ** by: The Resource Compiler for Qt version 4.5.2 ** ** WARNING! All changes made in this file will be lost! *****************************************************************************/ #include static const unsigned char qt_resource_data[] = { // /export/users/mantiuk/projects/pfstools/src/pfsview/icons/appicon.png 0x0,0x0,0x6,0xad, 0x89, 0x50,0x4e,0x47,0xd,0xa,0x1a,0xa,0x0,0x0,0x0,0xd,0x49,0x48,0x44,0x52,0x0, 0x0,0x0,0x20,0x0,0x0,0x0,0x20,0x8,0x6,0x0,0x0,0x0,0x73,0x7a,0x7a,0xf4, 0x0,0x0,0x0,0x4,0x73,0x42,0x49,0x54,0x8,0x8,0x8,0x8,0x7c,0x8,0x64,0x88, 0x0,0x0,0x0,0x9,0x70,0x48,0x59,0x73,0x0,0x0,0x11,0x77,0x0,0x0,0x11,0x77, 0x1,0x40,0x71,0x9a,0xd1,0x0,0x0,0x0,0x19,0x74,0x45,0x58,0x74,0x53,0x6f,0x66, 0x74,0x77,0x61,0x72,0x65,0x0,0x77,0x77,0x77,0x2e,0x69,0x6e,0x6b,0x73,0x63,0x61, 0x70,0x65,0x2e,0x6f,0x72,0x67,0x9b,0xee,0x3c,0x1a,0x0,0x0,0x6,0x2a,0x49,0x44, 0x41,0x54,0x58,0x85,0xa5,0x97,0x7f,0x6c,0xd4,0xf5,0x19,0xc7,0x5f,0xcf,0xdd,0xf5, 0xc7,0xf5,0xee,0x68,0xaf,0xd7,0x5e,0xab,0xbd,0xb2,0x62,0x40,0xa0,0x2d,0x8c,0x81, 0x48,0xe7,0x18,0x68,0x36,0x8a,0x58,0x86,0xd5,0x75,0xcb,0x48,0x16,0x45,0x8c,0xcc, 0x69,0x36,0x32,0xb3,0x2c,0x59,0xc,0xba,0x29,0xcb,0x96,0x65,0x31,0x51,0x97,0xf9, 0x3,0xd4,0x18,0x67,0x34,0xc6,0x8a,0x41,0x87,0x85,0x4e,0x46,0xeb,0x1a,0x6,0x13, 0xa6,0xa5,0x45,0x59,0x6,0xa5,0xa5,0xa5,0xbf,0xef,0x68,0xaf,0x77,0x6d,0xef,0xd7, 0xb3,0x3f,0xae,0x7,0x77,0xed,0xb7,0x47,0xbb,0xbd,0x93,0xcf,0x1f,0x9f,0xe7,0x79, 0xbe,0xcf,0xf3,0xfe,0x3e,0x9f,0xcf,0xe7,0x79,0x3e,0x1f,0x51,0x55,0xfe,0x1f,0x88, 0xc8,0x9a,0x22,0x2b,0xd,0xf6,0xc,0x7c,0xa1,0x28,0x27,0xbb,0xc6,0xf4,0x87,0xf3, 0x72,0xa0,0xaa,0x69,0x7,0x50,0xee,0xb1,0x71,0x34,0x2f,0x93,0x5a,0x23,0xfd,0x57, 0x1c,0x34,0x5d,0xd8,0x8e,0xea,0x2e,0xb4,0xd2,0xc9,0xa5,0xeb,0xf9,0x9b,0x3e,0x4c, 0xe9,0xc8,0xb9,0xb2,0x65,0xf7,0xfa,0x62,0x9a,0xeb,0x37,0x71,0xc7,0xb2,0x3c,0x5e, 0xb4,0x67,0xc8,0xb7,0xa7,0xfd,0xfd,0xf2,0x4a,0x27,0x4b,0x17,0x39,0xe2,0xf3,0x12, 0x1b,0x66,0x11,0x29,0x98,0x4f,0x2,0xd2,0x12,0x18,0x8b,0xe0,0xbf,0xff,0x66,0x16, 0xdc,0xea,0x86,0xc6,0x1a,0x8a,0x6e,0xce,0xe3,0x8d,0x6c,0x8b,0x7c,0x3d,0xa1,0xf7, 0xd8,0xf8,0xc3,0x6f,0x6f,0xa5,0x28,0x31,0x2f,0x77,0x92,0x5,0x2c,0x9a,0xee,0x47, 0x44,0xec,0x22,0xb2,0x74,0xde,0x4,0x42,0x51,0x4e,0x7f,0xd2,0x87,0xf,0xc0,0x9e, 0x1,0x47,0xb7,0x52,0xbc,0x24,0x97,0xf7,0xca,0x1c,0xd2,0xba,0xdc,0x29,0x17,0x6b, 0x16,0x52,0xb5,0x22,0xff,0x9a,0x7d,0x85,0x93,0xdc,0xc,0x13,0x2b,0x93,0x2,0x9b, 0xb,0xad,0xf2,0xb3,0xc5,0xb,0xf8,0x62,0x49,0x2e,0xc7,0x92,0xc9,0x5f,0xb5,0x49, 0xb7,0x9,0x45,0x24,0xa3,0xc2,0x49,0x67,0xdb,0xf7,0xb8,0x21,0x21,0x1b,0x8f,0x80, 0xc5,0x4,0x19,0x6,0xd4,0x7b,0x83,0xb0,0xab,0x99,0xfe,0x36,0x1f,0xad,0x7d,0x41, 0xde,0x72,0x5b,0x79,0x7c,0x77,0x25,0xc5,0x3f,0xa9,0xc4,0x36,0x1a,0x82,0xd5,0xef, 0x31,0xd0,0x35,0xc6,0x4a,0x55,0xed,0x4f,0x7c,0x63,0x49,0x97,0x1,0x55,0xd,0x7b, 0xec,0x32,0x19,0x53,0x30,0x49,0x5c,0x66,0x4d,0xf3,0xc5,0xd,0x39,0xf0,0xc1,0x9d, 0x14,0xb5,0xfb,0xd8,0x74,0xf0,0x22,0xeb,0x1f,0xa9,0xc0,0x9a,0x9b,0x39,0x45,0x3c, 0xa,0x31,0x65,0x30,0x39,0x78,0xa,0x1,0x11,0xb9,0x17,0x18,0x4,0x5a,0x55,0x75, 0x4,0xc0,0x63,0x97,0x3d,0x5b,0x4a,0xc9,0x4d,0x4,0x9f,0x2b,0x2a,0x9c,0x50,0xe1, 0xc4,0x9a,0x2c,0x7b,0xec,0x38,0xc3,0xbd,0x41,0x1e,0x99,0x6e,0x6b,0x99,0xa,0xbe, 0x62,0x9d,0x9b,0x7d,0xdf,0x28,0x86,0x13,0x3,0x4c,0x2c,0xc9,0x95,0xc9,0x60,0x84, 0xb1,0xcd,0xa5,0x78,0xf6,0x6d,0xc0,0x69,0x14,0x64,0x22,0xa,0x9f,0xd,0x83,0x2a, 0x7c,0xd5,0x5,0x39,0x69,0x32,0x73,0xd6,0x7,0x2d,0xfd,0x9c,0x8b,0xc4,0xb4,0xd9, 0x90,0xc0,0x42,0x3b,0x7f,0x7c,0x65,0x23,0xf9,0x15,0x49,0xa1,0xfc,0x61,0x70,0x64, 0xcc,0x74,0x56,0xdf,0x41,0x78,0xcf,0x3f,0x19,0xc,0x46,0x19,0x32,0xc1,0x71,0x93, 0x10,0x5,0xaa,0xb2,0xcc,0x14,0x3d,0x75,0xb,0xee,0xef,0x2e,0x62,0xc6,0x57,0xcf, 0xb7,0xe3,0xbb,0x1c,0x60,0xaf,0x11,0x39,0x51,0x55,0x6e,0xb4,0xc9,0x13,0xeb,0x8b, 0xd9,0xfd,0xd6,0xb7,0xc8,0x37,0xcf,0x92,0xee,0xd1,0x10,0xec,0x38,0x86,0xf7,0xd4, 0x10,0x7f,0xe9,0x1a,0xe3,0x47,0xaa,0x3a,0x9e,0xe2,0x48,0xc4,0xe6,0xb1,0xf1,0xf2, 0xea,0x2,0x36,0xbf,0x71,0x7,0xae,0x5,0x99,0xd7,0x74,0x6d,0x5e,0xa8,0x69,0xa0, 0xb1,0xd3,0xaf,0xd5,0x86,0x4,0x0,0x8a,0x72,0xe4,0xd1,0xaf,0x15,0xf0,0xab,0x83, 0x9b,0x29,0xc8,0x34,0xd8,0xe1,0xf7,0x1e,0xc1,0xdb,0xd8,0xc3,0x3,0xfe,0x90,0x1e, 0x34,0xa6,0x18,0x87,0x23,0x53,0xb6,0x55,0x7b,0x78,0xb5,0x7e,0x13,0xae,0x64,0xf9, 0xd6,0x6,0x86,0x3e,0x1f,0xe6,0xb5,0xbe,0x71,0x4e,0x45,0x62,0x5c,0x4,0xba,0x80, 0xbe,0x94,0x63,0xe8,0xca,0x96,0x1f,0x94,0x3b,0x79,0xf6,0x6f,0x5b,0x71,0x5b,0x92, 0x48,0xd4,0x77,0x10,0x7e,0xec,0x38,0x6f,0x77,0xfa,0xf5,0xbe,0x74,0xc1,0x13,0xb8, 0x69,0x81,0xec,0xff,0x7d,0x15,0xf7,0xd5,0x25,0x2d,0xc7,0x95,0x10,0x9c,0x1a,0x84, 0xe,0x3f,0x91,0x2f,0xaf,0x30,0x7a,0xd6,0xc7,0xe4,0xe7,0xc3,0xb4,0xcd,0xa8,0x3, 0x65,0xe,0x39,0x73,0x7e,0x3b,0x95,0xc9,0x4b,0x51,0xfe,0xe,0x97,0xbf,0xb8,0xc2, 0xe2,0xe9,0x69,0x9f,0xd,0x22,0x92,0xb9,0x3c,0x8f,0x8e,0xb3,0xdf,0xe7,0xc6,0x74, 0x76,0x8b,0xdf,0xe6,0x3f,0x33,0x92,0x6d,0xb5,0x60,0x4f,0xe,0x3e,0x11,0x85,0x60, 0x94,0xa1,0xb9,0x6,0x7,0x50,0xd5,0xd0,0x78,0x94,0x81,0x40,0x64,0x76,0x9b,0x23, 0xdd,0x44,0x7d,0x93,0x38,0xae,0x12,0x10,0x11,0x87,0xc7,0x2e,0xbf,0x59,0x96,0x87, 0x3d,0xd9,0xf0,0xb3,0x61,0x30,0xc1,0xf1,0xb9,0x6,0x4f,0x20,0x1c,0xe3,0xd8,0xbf, 0x86,0x52,0x65,0x31,0x85,0x77,0x2e,0x10,0x5e,0xf1,0x2e,0x3d,0x3b,0x9b,0x78,0xc1, 0x3b,0x49,0xa5,0x45,0x44,0xa,0x4a,0x6c,0x3c,0x59,0x99,0xcf,0x3d,0x7b,0x56,0xe3, 0xae,0x33,0x38,0x46,0xff,0xb,0x4c,0x82,0x79,0x7a,0x1,0xab,0x6b,0x64,0xe8,0xe4, 0x20,0x2f,0xf7,0x4,0xf8,0x9d,0xaa,0xfa,0x1,0x2c,0xf9,0x59,0xb4,0xbd,0xba,0x91, 0x82,0x6a,0xf,0x66,0x23,0x47,0xab,0x5c,0x10,0x83,0x19,0x4d,0xe4,0x7a,0xb0,0x8, 0xb7,0xad,0x72,0xa5,0xca,0xce,0x8d,0x10,0xe8,0x1e,0xd3,0xc7,0x53,0x88,0x3a,0xb3, 0xf0,0xcf,0x16,0x1c,0x20,0xdb,0xc,0x56,0x33,0x2e,0x11,0xb1,0xce,0x66,0x33,0x1d, 0x22,0x62,0xcb,0x36,0x53,0x94,0x5c,0x1d,0xa3,0xa,0xc1,0x8,0x81,0xe9,0xb6,0xa6, 0xf1,0x8,0x1d,0x77,0x7d,0x44,0xef,0xcf,0xff,0x81,0x77,0xff,0x97,0x44,0x3e,0xee, 0x81,0x91,0x50,0xaa,0xd1,0xde,0xb5,0xb8,0x4b,0x6c,0xec,0x9f,0x2b,0x81,0x12,0x1b, 0x2f,0x3d,0xbd,0xf6,0xda,0x3d,0x1,0xe2,0x25,0xbb,0xcc,0x41,0x61,0xa1,0x55,0x52, 0x8e,0xb2,0x4c,0x8d,0x62,0x60,0xa1,0xc5,0x44,0x59,0xb1,0x95,0x35,0xb7,0x14,0xb2, 0xe3,0x40,0x35,0x85,0xc9,0x86,0xb5,0x47,0xf0,0xfe,0xb5,0x9b,0x7,0xc7,0xc2,0xfa, 0x7e,0xba,0xe0,0xf6,0x4c,0xb9,0x7b,0x53,0x9,0xaf,0x1c,0xa8,0xc6,0x35,0x5d,0x17, 0x8e,0x41,0xed,0x11,0x86,0x3e,0x1d,0x64,0x6f,0x7f,0x50,0x9f,0x85,0x59,0xee,0x3, 0x65,0xe,0x39,0x7c,0x68,0xb,0xd5,0xe5,0x49,0xbd,0x61,0x34,0x4,0x3b,0x9a,0xf0, 0x9e,0x1e,0xe4,0x83,0xce,0x31,0x7e,0x6c,0x50,0x8a,0xad,0xa5,0x36,0x5e,0x5c,0x53, 0x48,0xcd,0xeb,0xb7,0xa7,0x96,0xe2,0x64,0xc4,0x14,0xb6,0x7f,0x8c,0xb7,0xb9,0x8f, 0xe7,0x7a,0x3,0xfa,0x6b,0x43,0x2,0x22,0xb2,0xe5,0xe1,0x72,0xde,0x7c,0x61,0xfd, 0xcc,0x4e,0x58,0xdf,0x41,0xf8,0x89,0x4f,0x19,0x8,0x46,0xe2,0xcd,0x8,0xe2,0x9b, 0xd4,0x6a,0xa6,0xe0,0xe9,0xb5,0xc6,0xcd,0x68,0x7a,0x63,0x6b,0xf7,0xc1,0x5d,0x1f, 0xd1,0xdc,0xe9,0xd7,0x8d,0x86,0x4,0x4a,0x6c,0xd2,0xd2,0x58,0xc3,0x6d,0xe5,0x86, 0x8d,0x38,0x8e,0x44,0x3b,0x86,0xf8,0x49,0xc9,0x9e,0x65,0x1b,0xef,0x6c,0xc2,0x77, 0xf8,0x12,0xdd,0x39,0x16,0xec,0x45,0x39,0x64,0xad,0x73,0x93,0xdd,0xd2,0x7,0x27, 0x6,0xb8,0x5d,0x55,0xcf,0xcc,0xe8,0xe2,0x22,0xf2,0xcd,0xba,0x9b,0x58,0x92,0x2e, 0x38,0xc4,0x3,0x56,0xb9,0xd3,0xdb,0x3c,0xd4,0x8c,0xef,0xf0,0x25,0x9e,0xef,0x9, 0xe8,0x93,0x53,0xbe,0x73,0x5b,0xfa,0x58,0x9,0x14,0xaa,0xea,0x19,0x30,0xd8,0x3, 0x25,0x76,0x39,0x73,0xa2,0x96,0x4a,0x8f,0x2d,0x3e,0x1f,0x9,0xc1,0x9f,0xda,0x19, 0xdf,0x56,0x86,0xb5,0xe2,0x3a,0xa4,0x92,0x11,0x53,0x28,0x7d,0x93,0xce,0x9e,0x80, 0x96,0xa5,0xb3,0x4b,0xe9,0x5,0x22,0x52,0x64,0x86,0x42,0xab,0x39,0xbe,0x63,0x9f, 0x69,0x25,0xb0,0xf2,0x5d,0xce,0x3f,0x75,0x9a,0x47,0xb7,0x36,0xd0,0xf8,0x9d,0x6, 0xfa,0x7b,0x83,0xc6,0x8e,0xc2,0xb1,0xf8,0x85,0xf5,0xaa,0x63,0x1,0x67,0x16,0x99, 0x22,0x92,0xb6,0xb2,0xa6,0x2c,0x81,0xaa,0xf6,0x67,0x98,0xa4,0xb6,0xea,0x7d,0xe, 0x0,0x91,0x2b,0x21,0x9e,0x19,0x9a,0xe0,0x39,0x55,0x8d,0x2,0xaf,0x65,0x9a,0xe5, 0xc1,0xf,0xbb,0x78,0xe9,0xa1,0x65,0xd7,0xa,0xd7,0xc3,0x9f,0xe0,0x6d,0xea,0xc5, 0x3f,0x11,0x65,0x24,0xc7,0x82,0xbb,0xe5,0x6e,0x8a,0xf3,0xa6,0x4e,0xc0,0xea,0x2, 0x2c,0xed,0x3e,0x96,0x3,0xad,0xb3,0x32,0x98,0xe5,0x39,0xb6,0x14,0xb0,0x1b,0xc8, 0xd7,0xfe,0xb4,0x12,0xaf,0xee,0x8a,0x3f,0xc5,0x5a,0xeb,0x50,0x8f,0x8d,0x43,0x9, 0x7d,0xa6,0x99,0xd,0xab,0x5c,0xf4,0xf9,0x1f,0x88,0xeb,0xf7,0x6d,0x20,0x94,0x65, 0x66,0xe7,0xbc,0x9f,0x66,0xaa,0x7a,0x4e,0x55,0xc7,0xc,0x54,0x1d,0x6d,0x3e,0x26, 0x13,0x93,0x5f,0x9e,0xa4,0xbf,0x3b,0xc0,0x2f,0x12,0xf3,0xc9,0x88,0x36,0x9f,0x1f, 0xe5,0xfe,0xea,0x43,0xc,0x9c,0x1c,0x80,0xd7,0xff,0xcd,0xa8,0xcd,0x92,0xda,0x5d, 0xe7,0x94,0x81,0x74,0x63,0x69,0x2e,0x97,0x75,0x17,0x7a,0x61,0x3b,0x5a,0x6a,0xe7, 0xef,0x46,0x36,0xf9,0x59,0xdc,0xe3,0xb1,0x71,0x14,0x28,0xbf,0x9e,0xbf,0xb4,0x2f, 0x23,0x23,0x2c,0x74,0xc8,0x9f,0x33,0x4c,0xac,0xb,0x84,0xc9,0xeb,0x1f,0x67,0x9b, 0xaa,0xce,0xfb,0xae,0x90,0x8c,0xff,0x2,0xb5,0x5d,0x16,0xc8,0x29,0xe8,0x39,0xee, 0x0,0x0,0x0,0x0,0x49,0x45,0x4e,0x44,0xae,0x42,0x60,0x82, }; static const unsigned char qt_resource_name[] = { // icons 0x0,0x5, 0x0,0x6f,0xa6,0x53, 0x0,0x69, 0x0,0x63,0x0,0x6f,0x0,0x6e,0x0,0x73, // appicon.png 0x0,0xb, 0xa,0xb1,0xba,0xa7, 0x0,0x61, 0x0,0x70,0x0,0x70,0x0,0x69,0x0,0x63,0x0,0x6f,0x0,0x6e,0x0,0x2e,0x0,0x70,0x0,0x6e,0x0,0x67, }; static const unsigned char qt_resource_struct[] = { // : 0x0,0x0,0x0,0x0,0x0,0x2,0x0,0x0,0x0,0x1,0x0,0x0,0x0,0x1, // :/icons 0x0,0x0,0x0,0x0,0x0,0x2,0x0,0x0,0x0,0x1,0x0,0x0,0x0,0x2, // :/icons/appicon.png 0x0,0x0,0x0,0x10,0x0,0x0,0x0,0x0,0x0,0x1,0x0,0x0,0x0,0x0, }; QT_BEGIN_NAMESPACE extern bool qRegisterResourceData (int, const unsigned char *, const unsigned char *, const unsigned char *); extern bool qUnregisterResourceData (int, const unsigned char *, const unsigned char *, const unsigned char *); QT_END_NAMESPACE int QT_MANGLE_NAMESPACE(qInitResources)() { QT_PREPEND_NAMESPACE(qRegisterResourceData) (0x01, qt_resource_struct, qt_resource_name, qt_resource_data); return 1; } Q_CONSTRUCTOR_FUNCTION(QT_MANGLE_NAMESPACE(qInitResources)) int QT_MANGLE_NAMESPACE(qCleanupResources)() { QT_PREPEND_NAMESPACE(qUnregisterResourceData) (0x01, qt_resource_struct, qt_resource_name, qt_resource_data); return 1; } Q_DESTRUCTOR_FUNCTION(QT_MANGLE_NAMESPACE(qCleanupResources)) pfstools-2.2.0/src/pfsview/pfsview.10000664000701400070140000000652514105165615016111 0ustar rkm38rkm38.TH "pfsview" 1 .SH NAME pfsview \- Viewer for high-dynamic range images in pfs format .SH SYNOPSIS .B pfsview [--window_min ] [--window_max ] .SH DESCRIPTION pfsview is a QT application for viewing high-dynamic range images. It expects pfs stream on the standard input and displays the frames in that stream one by one. \fBpfsv\fR script can be more convienent to use if hdr images are to be displayed without any prior processing. .SH DYNAMIC RANGE WINDOW To show high-dynamic range data on a low-dynamic range monitor, pfsview uses concept of a dynamic range window. The dynamic range window is the highest and lowest value that should be mapped to black and white pixel. Values above or below the window are clipped (see clipping methods below). The dynamic range window is displayed in pfsview as a blue area on the dynamic range scale (second toolbox from the top). The window can be moved, shrunk and expended using a mouse or a keyboard. .SH CLIPPING METHODS Currently, two clipping methods are available (see View menu): .TP Simple clipping The values above and below the dynamic range window are displayed as black or white. .TP Color-coded clipping The values above the dynamic range window are displayed as yellow and below the window as green. This is helpful to see which parts of the image do not fit into the selected dynamic range. .TP Keep brightness and hue This method tries to preserve brightness and hue while sacrificing color saturation when the colors exceed the RGB color gamut. Colors are desaturated in the RGB color space towards the neutral color (D65) of the corresponding luminance. .SH MAPPING METHODS High-dynamic range data are usually better visualized using non-linear scale, for example a logarithmic or a power function. pfsview offers several such scales, shown in \fIView\fR menu. Gray-scale values for each mapping method are computed by the formulas: \fBLINEAR\fR: y = (x-min)/(max-min) \fBGAMMA\fR: y = [ (x-min)/(max-min) ]^gamma \fBLOGARITHMIC\fR: y = (log10(x)-log10(min))/(log10(max)-log10(min)) where \fIy\fR is the gray-scale value after mapping, \fIx\fR is an input HDR value, \fImin\fR and \fImax\fR are lower and upper bounds of the dynamic range window. .SH REPRODUCING MAPPING WITH PFSGAMMA To reproduce the displayed image and save it to LDR image using pfs commands, you can use the following commands: pfsin image.hdr | pfsgamma -g 2.2 -m 0.01 | pfsout image.png where "2.2" is the gamma value from the "Mapping" combo box, and 0.01 is the exposure value. The exposure is shown as the right-most number in the window status bar of pfsview. .SH OPTIONS .TP --window_min Lower bound of the values that should be displayed or minimum value of the dynamic range window. The value should be given in log_10 units, for example -1 if the lower bound should be 0.1 (10^-1). .TP --window_max Upper bound of the values that should be displayed or minimum value of the dynamic range window. The value should be given in log_10 units, for example -1 if the upper bound should be 0.1 (10^-1). .SH EXAMPLES .TP pfsin memorial.hdr | pfsview See the memorial image. .TP pfsv memorial.hdr The same as above, but using the utility script 'pv'. .SH "SEE ALSO" .BR pfsv (1) .BR pfsin (1) .SH BUGS Zomming in may sometimes show artifacts. Please report bugs and comments to Rafal Mantiuk . pfstools-2.2.0/src/pfsview/luminancerange_widget.cpp0000664000701400070140000002172114105165615021376 0ustar rkm38rkm38/** * @brief * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Rafal Mantiuk and Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Rafal Mantiuk, * * $Id: luminancerange_widget.cpp,v 1.6 2013/12/21 19:42:28 rafm Exp $ */ #include #include #include #include #include #include #include #include //#include #include "luminancerange_widget.h" #include "histogram.h" static const float exposureStep = 0.25f; static const float shrinkStep = 0.1f; static const float minWindowSize = 0.1f; static const int dragZoneMargin = 5; // How many pizels from the range window border should draging be activated #define min(x,y) ( (x)<(y) ? (x) : (y) ) #define max(x,y) ( (x)>(y) ? (x) : (y) ) LuminanceRangeWidget::LuminanceRangeWidget( QWidget *parent ): QFrame( parent ), histogram( NULL ), histogramImage( NULL ), showVP( false ) { setFrameShape( QFrame::Box ); setMouseTracking( true ); minValue = -6; maxValue = 8; windowMin = 0; windowMax = 2; mouseDragStart = -1; dragShift = 0; } LuminanceRangeWidget::~LuminanceRangeWidget() { delete histogram; } QSize LuminanceRangeWidget::sizeHint () const { return QSize( 300, 22 ); } #define getWindowX( x ) ((int)((x-minValue) / (maxValue-minValue) * (float)fRect.width()) + fRect.left()) void LuminanceRangeWidget::paintEvent( QPaintEvent *pe ) { { QPainter p( this ); QRect fRect = getPaintRect(); if( fRect.width() < 50 ) // Does not make sense to paint anything return; // Paint range window { int x1, x2; x1 = getWindowX( draggedMin() ); x2 = getWindowX( draggedMax() ); QColor selectionColor = mouseDragStart == DRAGNOTSTARTED ? QColor( 0, 100, 255 ) : QColor( 0, 150, 255 ); p.fillRect( x1, fRect.top(), x2-x1, fRect.height(), QBrush( selectionColor ) ); } // Paint histogram if( histogramImage != NULL ) { if( histogram == NULL || histogram->getBins() != fRect.width() ) { delete histogram; // Build histogram from at least 5000 pixels int accuracy = histogramImage->getRows()*histogramImage->getCols()/5000; if( accuracy < 1 ) accuracy = 1; histogram = new Histogram( fRect.width(), accuracy ); histogram->computeLog( histogramImage, minValue, maxValue ); } float maxP = histogram->getMaxP(); int i = 0; p.setPen( Qt::green ); for( int x = fRect.left(); i < histogram->getBins(); x++, i++ ) { if( histogram->getP(i) > 0 ) { int barSize = (int)((float)fRect.height() * histogram->getP(i)/maxP); p.drawLine( x, fRect.bottom(), x, fRect.bottom() - barSize ); } } } // Paint scale QFont labelFont( "SansSerif", 8 ); p.setFont( labelFont ); p.setPen( Qt::black ); QRect textBounding = p.boundingRect( fRect, Qt::AlignHCenter|Qt::AlignBottom, "-8" ); for( float x = ceil( minValue ); x <= floor( maxValue ); x++ ) { int rx = getWindowX(x); p.drawLine( rx, fRect.top(), rx, textBounding.top() ); char str[14]; sprintf( str, "%g", x ); p.drawText( rx-20, textBounding.top(), 40, textBounding.height(), Qt::AlignHCenter|Qt::AlignBottom, str ); } // Paint value pointer if( showVP ) { int x = getWindowX( valuePointer ); if( fRect.contains( x, fRect.y() ) ) { p.setPen( Qt::yellow ); p.drawLine( x, fRect.top(), x, fRect.bottom() ); } } } QFrame::paintEvent(pe); } float LuminanceRangeWidget::draggedMin() { if( mouseDragStart == DRAGNOTSTARTED ) return windowMin; if( dragMode == DRAG_MIN ) { float draggedPos = windowMin+dragShift; return min( draggedPos, windowMax - minWindowSize ); } else if( dragMode == DRAG_MINMAX ) { return windowMin+dragShift; } return windowMin; } float LuminanceRangeWidget::draggedMax() { if( mouseDragStart == DRAGNOTSTARTED ) return windowMax; if( dragMode == DRAG_MAX ) { float draggedPos = windowMax+dragShift; return max( draggedPos, windowMin + minWindowSize ); } else if( dragMode == DRAG_MINMAX ) { return windowMax+dragShift; } return windowMax; } void LuminanceRangeWidget::mousePressEvent ( QMouseEvent * me ) { if( dragMode == DRAG_NO ) return; mouseDragStart = me->x(); dragShift = 0; update(); } void LuminanceRangeWidget::mouseReleaseEvent ( QMouseEvent * ) { float newWindowMin = draggedMin(); float newWindowMax = draggedMax(); mouseDragStart = DRAGNOTSTARTED; windowMin = newWindowMin; windowMax = newWindowMax; dragShift = 0; update(); updateRangeWindow(); } void LuminanceRangeWidget::mouseMoveEvent( QMouseEvent *me ) { // printf( "MouseButton: %d\n", me->button() ); if( (me->buttons() & Qt::LeftButton) != 0 && dragMode != DRAG_NO ) { if( mouseDragStart != DRAGNOTSTARTED ) { QRect fRect = getPaintRect(); int windowCordShift = me->x() - mouseDragStart; dragShift = (float)windowCordShift / (float)fRect.width() * (maxValue - minValue); update(); } } else { QRect fRect = rect(); int winBegPos = getWindowX( windowMin ); int winEndPos = getWindowX( windowMax ); if( abs( me->x() - winBegPos ) < dragZoneMargin ) { setCursor( QCursor( Qt::SplitHCursor ) ); dragMode = DRAG_MIN; } else if( abs( me->x() - winEndPos ) < dragZoneMargin ) { setCursor( QCursor( Qt::SplitHCursor ) ); dragMode = DRAG_MAX; } else if( me->x() > winBegPos && me->x() < winEndPos ) { setCursor( QCursor( Qt::SizeHorCursor ) ); dragMode = DRAG_MINMAX; } else { unsetCursor(); dragMode = DRAG_NO; } } } void LuminanceRangeWidget::decreaseExposure() { windowMin -= exposureStep; windowMax -= exposureStep; update(); updateRangeWindow(); } void LuminanceRangeWidget::increaseExposure() { windowMin += exposureStep; windowMax += exposureStep; update(); updateRangeWindow(); } void LuminanceRangeWidget::extendRange() { if( windowMax - windowMin > 10 ) return; windowMin -= shrinkStep; windowMax += shrinkStep; update(); updateRangeWindow(); } void LuminanceRangeWidget::shrinkRange() { if( windowMax - windowMin < 0.19 ) return; windowMin += shrinkStep; windowMax -= shrinkStep; update(); updateRangeWindow(); } void LuminanceRangeWidget::setHistogramImage( const pfs::Array2D *image ) { histogramImage = image; delete histogram; histogram = NULL; update(); } void LuminanceRangeWidget::fitToDynamicRange() { if( histogramImage != NULL ) { float min = 99999999; float max = -99999999; int size = histogramImage->getRows()*histogramImage->getCols(); for( int i = 0; i < size; i++ ) { float v = (*histogramImage)(i); if( v > max ) max = v; else if( v < min ) min = v; } if( min <= 0.000001 ) min = 0.000001; // If data contains negative values windowMin = log10( min ); windowMax = log10( max ); if( windowMax - windowMin < 0.5 ) { // Window too small float m = (windowMin + windowMax)/2.f; windowMax = m + 0.25; windowMin = m - 0.25; } update(); updateRangeWindow(); } } void LuminanceRangeWidget::lowDynamicRange() { windowMin = -2.0f; windowMax = 0.0f; update(); updateRangeWindow(); } QRect LuminanceRangeWidget::getPaintRect() const { QRect fRect = frameRect(); fRect.setLeft( fRect.left()+1 ); fRect.setTop( fRect.top()+1 ); fRect.setBottom( fRect.bottom()-1 ); fRect.setRight( fRect.right()-1 ); return fRect; } void LuminanceRangeWidget::showValuePointer( float value ) { QRect fRect = getPaintRect(); int oldx = showVP ? getWindowX( valuePointer ) : -1; valuePointer = value; showVP = true; int newx = getWindowX( valuePointer ); if( oldx == -1 ) oldx = newx; QRect updateRect( min( oldx, newx ), fRect.top(), max( oldx, newx ) - min(oldx, newx )+1, fRect.height() ); update( updateRect ); } void LuminanceRangeWidget::setRangeWindowMinMax( float min, float max ) { assert( min < max ); windowMin = min; windowMax = max; update(); updateRangeWindow(); } void LuminanceRangeWidget::hideValuePointer() { showVP = false; update(); } pfstools-2.2.0/src/pfsview/main.h0000664000701400070140000000530014105165615015427 0ustar rkm38rkm38/** * @brief * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Rafal Mantiuk and Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Rafal Mantiuk, * * $Id: main.h,v 1.11 2011/03/21 16:09:16 rafm Exp $ */ #ifndef MAIN_H #define MAIN_H #include #include #include #include #include #include #include #include #include "pfsview_widget.h" #define MAX_FRAMES_IN_MEMORY 5 class LuminanceRangeWidget; class QComboBox; //class pfs::Frame; enum ColorCoord { CC_RGB = 0, CC_XYZ, CC_Yupvp, CC_Yxy, CC_COUNT }; class PFSViewMainWin : public QMainWindow { Q_OBJECT public: PFSViewMainWin( float window_min, float window_max ); public slots: // void changeClippingMethod( int clippingMethod ); void updatePointerValue(); void updateRangeWindow(); void updateChannelSelection(); void setChannelSelection( int selectedChannel ); void updateZoomValue(); void gotoNextFrame(); void gotoPreviousFrame(); void saveImage(); void copyImage(); void setLumMappingMethod( int method ); void setLumMappingMethod( QAction *action ); void setColorCoord( QAction *action ); void showAboutdialog(); void updateViewSize(); private: void setFrame( pfs::Frame *frame ); QAction *mappingAct[6]; QLabel *pointerPosAndVal, *zoomValue, *exposureValue; PFSViewWidget *pfsView; LuminanceRangeWidget *lumRange; QComboBox *channelSelection; QComboBox *mappingMethodCB; ColorCoord colorCoord; bool readNextFrame(); void updateMenuEnable( ); std::list frameList; std::list::iterator currentFrame; public: PFSViewWidget *getPFSViewWidget() { return pfsView; } }; #endif pfstools-2.2.0/src/pfsview/pfsv.in0000664000701400070140000000043214105165615015641 0ustar rkm38rkm38#!@BASH_PATH@ ############################################################ # View HDR images ############################################################ if [ -z "$1" ]; then echo "View HDR images"; echo "Usage: pfsv [...]" exit 0; fi pfsin "$@" | pfsview pfstools-2.2.0/src/pfsview/icons/0002775000701400070140000000000014105165615015451 5ustar rkm38rkm38pfstools-2.2.0/src/pfsview/icons/appicon.svg0000664000701400070140000001032314105165615017620 0ustar rkm38rkm38 image/svg+xml pfstools-2.2.0/src/pfsview/icons/appicon.png0000664000701400070140000000325514105165615017613 0ustar rkm38rkm38‰PNG  IHDR szzôsBIT|dˆ pHYsww@qšÑtEXtSoftwarewww.inkscape.org›î<*IDATX…¥—lÔõÇ_ÏÝõÇõîh¯×^«½²b@ -ŒHçh6ŠX†ÕuËHEŒÌi62³,Y º)Ë–e1Q—ùÔg4ÆŠA‡…NF릥EY¥¥¥¿ïh¯wmï׳?®wí·G»½“ÏŸçy¾Ïóþ>ŸÏçy>QUþˆÈš"+ ö |¡('»Æô‡ór ªiPî±q4/“Z#ýW4]ØŽê.´ÒÉ¥ëù›>Léȹ²e÷úbšë7qDz<^´gÈ·§ýýòJ'K9âóf)˜OÒ‹à¿ÿfÜê†ÆŠnÎãl‹|=¡÷ØøÃoo¥(1/w’,šîGDì"²tÞBQNÒ‡ÀžG·R¼$—÷ÊÒºÜ)kRµ"ÿš}…“Ü +“› ­ò³Å øbI.Ç’É_µI· E$£ÂIgÛ÷¸!!€ÅÔ{ƒ°«™þ6­}AÞr[y|w%Å?©Ä6‚Õï1Ð5ÆJUíO|cI—U {ì2S0I\fMóÅ 9ðÁµûØtð"ë©Àš›9E< 1e09x ¹ZUuÀc—=[JÉMŸ+*œPáÄš,{ì8ýA™nk™ ¾b›}ß(†L,É•É`„±Í¥xömÀid" Ÿ ƒ*|Õ9i2sÖ-ýœ‹Ä´ÙÀB;|e#ùI¡üapdÌtVßAxÏ? F2Áq“ª²Ì=u îï.bÆWÏ·ã»`¯9QUn´Éë‹ÙýÖ·È7Ï’îÑì8†÷ÔéãGª:žâHÄæ±ñòê6¿q®™×tm^¨i ±Ó¯Õ†Šräѯ𫃛)È4Øá÷ÁÛØÃþ4¦‡#S¶U{xµ~®dùÖ†>æµ¾qNEb\º€¾”cèÊ–”;yöo[q[’HÔw~ì8owúõ¾tÁ¸iìÿ}÷Õ%-Ç•œ„?‘/¯0zÖÇäçôͨe9s~;•ÉKQþ—¿¸ÂâéiŸ "’¹<޳ßçÆtv‹ßæ?3’mµ`O>…`”¡¹PÕÐx”@dv›#ÝD}“8®‡Ç.¿Y–‡=Ùð³a0Áñ¹O ãØ¿†Re1…w.^ñ.=;›xÁ;I¥ED Jlî‘PªÑÞµ¸KlìŸ+/=½öÚ=â%»ÌAa¡UR޲Lb`¡ÅDY±•5·²ã@5…ɆµGðþµ›ÇÂú~ºàöL¹{S ¯¨Æ5]ŽAí†>doPŸ…Yîe9|h ÕåI½a4;šðžäƒÎ1~lPŠ­¥6^\SHÍë·§–âdĶŒ·¹çzúkC"²åárÞ|aýÌNXßAø‰OFâÍâ›Ôj¦à鵯Íhzck÷Á]ÑÜé׆JlÒÒXÃmå†8ŽD;†øIÉžeïlÂwøÝ9ìE9d­s“ÝÒ'¸]UÏÌèâ"òͺ›X’.8ÄV¹ÓÛ<ÔŒïð%žï è“S¾s[úX ªê0Ø%v9s¢–J-> ÁŸÚßV†µâ:¤’S(}“Ξ€–¥³Ké"Rd†B«9¾cŸi%°ò]Î?ušG·6Ðøú{ƒÆŽÂ±ø…õªcg™"’¶²¦,ªög˜¤¶ê}‘+!žšà9U¯ešåÁ»xé¡e× ×ßàmêÅ?e$Ç‚»ånŠó¦NÀê,í>–­³2˜å9¶°È×þ´¯îŠ?ÅZëPC }¦™ «\ôùˆë÷m ”ef缟fªzNUÇ Tm>&“_ž¤¿;À/óɈ6ŸåþêC œ€×ÿͨ͒Ú]ç”tci.—uza;ZjçïF6ùYÜã±q(¿ž¿´/##,tÈŸ3L¬ „Éëg›ªÎû®Œÿµ]È)è9îIEND®B`‚pfstools-2.2.0/src/pfsview/pfsview_widget.cpp0000664000701400070140000005452114105165615020075 0ustar rkm38rkm38/** * @brief * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Rafal Mantiuk and Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Rafal Mantiuk, * * $Id: pfsview_widget.cpp,v 1.26 2014/10/13 09:27:38 rafm Exp $ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pfsview_widget.h" //#include "pfsglviewwidget.h" #include #define min(x,y) ( (x)<(y) ? (x) : (y) ) #define max(x,y) ( (x)>(y) ? (x) : (y) ) #define D65_LUM_R 0.212656f #define D65_LUM_G 0.715158f #define D65_LUM_B 0.072186f inline float clamp( float val, float min, float max ) { if( val < min ) return min; if( val > max ) return max; return val; } inline int clamp( int val, int min, int max ) { if( val < min ) return min; if( val > max ) return max; return val; } const char* COLOR_CHANNELS = "Color"; PFSViewWidgetArea::PFSViewWidgetArea( QWidget *parent ) : QScrollArea( parent ) { PFSViewWidget *pfsview = new PFSViewWidget( this ); setWidget( pfsview ); setWidgetResizable( true ); setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Preferred ); } QSize PFSViewWidgetArea::sizeHint() const { QSize sz = widget()->sizeHint(); // printf( "widget: %d %d\n", sz.width(), sz.height() ); sz += QSize( frameWidth()*2, frameWidth()*2 ); // printf( "area: %d %d\n", sz.width(), sz.height() ); return sz; } PFSViewWidget::PFSViewWidget( QWidget *parent ) : QWidget( parent ), image( NULL ), minValue( 1.f ), maxValue( 100.f ), zoom( 1.f ), mappingMethod( MAP_GAMMA2_2 ), visibleChannel( COLOR_CHANNELS ), negativeTreatment( NEGATIVE_BLACK ), infNaNTreatment( INFNAN_MARK_AS_RED ), updateMappingRequested( true ), clippingMethod(CLIP_SIMPLE) { fit_to_content_mode = true; setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ); setMouseTracking( true ); setCursor( QCursor( Qt::CrossCursor ) ); //name, Qt::WNoAutoErase // setResizePolicy( Manual ); pfsFrame = NULL; workArea[0] = workArea[1] = workArea[2] = NULL; selection += QRegion( 10, 10, 100, 100 ); pointerValue.valid = false; } PFSViewWidget::~PFSViewWidget() { delete pfsFrame; delete workArea[0]; delete workArea[1]; delete workArea[2]; } // ======================= Set current frame ========================== void PFSViewWidget::setFrame( pfs::Frame *frame ) { pfsFrame = frame; if( frame == NULL ) return; // If selected channel not available if( ( visibleChannel == COLOR_CHANNELS && !hasColorChannels( frame ) ) || ( visibleChannel != COLOR_CHANNELS && frame->getChannel( visibleChannel ) == NULL ) ) { // Chose first available channel pfs::ChannelIterator *it = frame->getChannels(); if( !it->hasNext() ) // TODO: failover throw new pfs::Exception( "No channels available!" ); visibleChannel = it->getNext()->getName(); } else if( visibleChannel != COLOR_CHANNELS ) { // Get a new pointer, as the old frame object // can be delete after returning from this method visibleChannel = frame->getChannel( visibleChannel )->getName(); } if( fit_to_content_mode ) { int desktopWidth, desktopHeight; desktopWidth = QApplication::desktop()->width(); desktopHeight = QApplication::desktop()->height(); if( frame->getWidth() > desktopWidth || frame->getHeight() > desktopHeight ) { zoom = min( (float)desktopWidth / (float)frame->getWidth(), (float)desktopHeight / (float)frame->getHeight() ); } } updateZoom(); setPointer(); } //void PFSViewWidget::drawContents( QPainter * p, int clipx, int clipy, int clipw, int cliph ) void PFSViewWidget::paintEvent( QPaintEvent *event ) { assert( pfsFrame != NULL ); QRect clip = event->rect(); int clipx = clip.x(); int clipy = clip.y(); int clipw = clip.width(); int cliph = clip.height(); if( updateMappingRequested ) updateMapping(); QPainter p( this ); // if( image != NULL ) p->drawImage( 0, 0, *image ); if( image != NULL ) { p.drawImage( clipx, clipy, *image, clipx, clipy, clipw, cliph ); //Erase area outside image if( clipx+clipw > image->width() ) p.eraseRect( image->width(), 0, clipx+clipw - image->width(), image->height() ); if( clipy+cliph > image->height() ) p.eraseRect( 0, image->height(), image->width(), clipy+cliph - image->height() ); if( clipx+clipw > image->width() && clipy+cliph > image->height() ) p.eraseRect( image->width(), image->height(), clipx+clipw - image->width(), clipy+cliph - image->height() ); } } // ======================= Update =========================== static void scaleCopyArray(const pfs::Array2D *from, pfs::Array2D *to) { assert( from->getRows() >= to->getRows() ); assert( from->getCols() >= to->getCols() ); float sx, sy; float dx = (float)from->getCols() / (float)to->getCols(); float dy = (float)from->getRows() / (float)to->getRows(); int x, y, i = 0; for( sy = 0, y = 0; y < to->getRows(); y++, sy += dy ) { for( sx = 0, x = 0; x < to->getCols(); x++, sx += dx ) { (*to)(i++) = (*from)((int)(sx), (int)(sy) ); } } } static void transformImageToWorkArea( pfs::Array2D **workArea, float zoom, bool color, pfs::Array2D *X, pfs::Array2D *Y = NULL, pfs::Array2D *Z = NULL ) { int origCols, origRows; origCols = X->getCols(); origRows = X->getRows(); int workCols, workRows; workCols = min( (int)((float)X->getCols() * zoom), X->getCols() ); workRows = min( (int)((float)X->getRows() * zoom), X->getRows() ); int requiredChannels = color ? 3 : 1; // Reallocate work area arrays to fit new size { for( int c = 0; c < requiredChannels; c++ ) { if( workArea[c] == NULL || workArea[c]->getCols() != workCols || workArea[c]->getRows() != workRows ) { if( workArea[c] != NULL ) delete workArea[c]; workArea[c] = new pfs::Array2DImpl( workCols, workRows ); } } } //Copy | rescale & tranform image to work area if( color ) { if( workCols == origCols && workRows == origRows ) { copyArray( X, workArea[0] ); copyArray( Y, workArea[1] ); copyArray( Z, workArea[2] ); } else { scaleCopyArray( X, workArea[0] ); scaleCopyArray( Y, workArea[1] ); scaleCopyArray( Z, workArea[2] ); } pfs::transformColorSpace( pfs::CS_XYZ, workArea[0], workArea[1], workArea[2], pfs::CS_RGB, workArea[0], workArea[1], workArea[2] ); } else { if( workCols == origCols && workRows == origRows ) copyArray( X, workArea[0] ); else scaleCopyArray( X, workArea[0] ); } } #define LUTSIZE 256 inline int binarySearchPixels( float x, const float *lut, const int lutSize ) { int l = 0, r = lutSize; while( true ) { int m = (l+r)/2; if( m == l ) break; if( x < lut[m] ) r = m; else l = m; } return l; } static float getInverseMapping( LumMappingMethod mappingMethod, float v, float minValue, float maxValue ) { switch( mappingMethod ) { case MAP_GAMMA1_4: return powf( v, 1.4 )*(maxValue-minValue) + minValue; case MAP_GAMMA1_8: return powf( v, 1.8 )*(maxValue-minValue) + minValue; case MAP_GAMMA2_2: return powf( v, 2.2 )*(maxValue-minValue) + minValue; case MAP_GAMMA2_6: return powf( v, 2.6 )*(maxValue-minValue) + minValue; case MAP_LINEAR: return v*(maxValue-minValue) + minValue; case MAP_LOGARITHMIC: return powf( 10, v * (log10f(maxValue) - log10f(minValue)) + log10f( minValue ) ); default: assert(0); return 0; } } /** * Copy pixels in work area to QImage using proper clipping and mapping * method. Handle also expansion if image in zoomed in. */ static void mapFrameToImage( pfs::Array2D *R, pfs::Array2D *G, pfs::Array2D *B, QImage *img, float minValue, float maxValue, RGBClippingMethod clippingMethod, LumMappingMethod mappingMethod, InfNaNTreatment infNaNTreatment, NegativeTreatment negativeTreatment ) { assert( R != NULL ); assert( img != NULL ); int rows = R->getRows(); int cols = R->getCols(); bool expand = cols != img->width() || rows != img->height(); // Zoom in float imgDeltaRow, imgDeltaCol; if( expand ) { imgDeltaRow = (float)(img->height()) / (float)rows; imgDeltaCol = (float)(img->width()) / (float)cols; } else { imgDeltaRow = imgDeltaCol = 1; } bool color = (G != NULL); assert( !color || (color && B != NULL) ); float lutPixFloor[257*2]; QRgb lutPixel[257*2]; int lutSize; if( !color && ( negativeTreatment == NEGATIVE_GREEN_SCALE || negativeTreatment == NEGATIVE_ABSOLUTE ) ) { // Handle negative numbers lutSize = 257*2+1; for( int p = 256; p >= 0; p-- ) { float p_left = (float)p/255.f; lutPixFloor[256-p+1] = -getInverseMapping( mappingMethod, p_left, minValue, maxValue ); if( p != 0 ) lutPixel[256-p+1] = negativeTreatment == NEGATIVE_GREEN_SCALE ? QColor( 0, p-1, 0 ).rgb() : QColor( p-1, p-1, p-1 ).rgb(); } for( int p = 0; p < 257; p++ ) { float p_left = (float)p/255.f; lutPixFloor[257+p+1] = getInverseMapping( mappingMethod, p_left, minValue, maxValue ); if( p != 256 ) lutPixel[257+p+1] = QColor( p, p, p ).rgb(); } if( clippingMethod == CLIP_COLORCODED ) { lutPixel[0] = negativeTreatment == NEGATIVE_GREEN_SCALE ? QColor( 0, 255, 255 ).rgb() : QColor( 255, 255, 0 ).rgb(); lutPixel[257] = QColor( 0, 0, 255 ).rgb(); lutPixel[257*2-1] = QColor( 255, 255, 0 ).rgb(); } else { lutPixel[0] = lutPixel[1]; lutPixel[257] = QColor( 0, 0, 0 ).rgb(); lutPixel[257*2-1] = QColor( 255, 255, 255 ).rgb(); } } else { // clip negative numbers int neg_offset = ((!color && negativeTreatment == NEGATIVE_MARK_AS_RED) ? 1 : 0); lutSize = 257+1 + neg_offset; // +1 - for lower bound for( int p = 1+neg_offset; p <= LUTSIZE+1; p++ ) { float p_left = ((float)p - 1.f - (float)neg_offset)/255.f; // Should be -1.5f, but we don't want negative nums lutPixFloor[p] = getInverseMapping( mappingMethod, p_left, minValue, maxValue ); if( !color && p < LUTSIZE+1+neg_offset ) lutPixel[p] = QColor( p-1-neg_offset, p-1-neg_offset, p-1-neg_offset ).rgb(); // printf( "p = %d\tl = %g\n", p, lookupTable[p] ); } if( clippingMethod == CLIP_COLORCODED ) { lutPixel[neg_offset] = QColor( 0, 0, 255 ).rgb(); lutPixel[LUTSIZE+1+neg_offset] = QColor( 255, 255, 0 ).rgb(); } else { lutPixel[neg_offset] = QColor( 0, 0, 0 ).rgb(); lutPixel[LUTSIZE+1+neg_offset] = QColor( 255, 255, 255 ).rgb(); } if( negativeTreatment == NEGATIVE_MARK_AS_RED && !color) { lutPixFloor[1] = 0; lutPixel[0] = QColor( 255, 0, 0 ).rgb(); } } // bool once = true; // #pragma omp parallel for private (index) #pragma omp parallel for for( int r = 0; r < rows; r++ ) { float imgRow, imgCol; int index = r*cols; imgRow = (float)r * imgDeltaRow; QRgb* line = (QRgb*)img->scanLine( (int)imgRow ); imgCol = 0; for( int c = 0; c < cols; c++, index++, imgCol += imgDeltaCol ) { QRgb pixel; if( color ) { // Color channels int pr, pg, pb; pr = binarySearchPixels( (*R)(index), lutPixFloor, lutSize ); pg = binarySearchPixels( (*G)(index), lutPixFloor, lutSize ); pb = binarySearchPixels( (*B)(index), lutPixFloor, lutSize ); // Clipping if( clippingMethod == CLIP_COLORCODED ) { if( pr == 0 || pg == 0 || pb == 0 ) pixel = lutPixel[0]; else if( pr == lutSize-1 || pg == lutSize-1 || pb == lutSize-1 ) pixel = lutPixel[LUTSIZE+1]; else pixel = QColor( pr-1, pg-1, pb-1 ).rgb(); } else if( clippingMethod == CLIP_KEEP_BRI_HUE ) { if( pr == lutSize-1 || pg == lutSize-1 || pb == lutSize-1 || pr == 0 || pg == 0 || pb == 0 ) { float p[3]; p[0] = (*R)(index); p[1] = (*G)(index); p[2] = (*B)(index); float gray = (p[0]+p[1]+p[2])/3.f; // float gray = D65_LUM_R*p[0]+D65_LUM_G*p[1]+D65_LUM_B*p[2]; float t; if( gray >= maxValue ) { pixel = QColor( 255, 255, 255 ).rgb(); } else if( gray <= minValue ) { pixel = QColor( 0, 0, 0 ).rgb(); } else { int i; for( i = 0; i < 3; i++ ) { t = (maxValue - p[i])/(gray - p[i]); if( t >= 0 && t <= 1 ) { break; } t = (minValue - p[i])/(gray - p[i]); if( t >= 0 && t <= 1 ) { break; } } if( i == 3 ) pixel = QColor( 255, 255, 255 ).rgb(); else { for( int i = 0; i < 3; i++ ) p[i] = gray*t + p[i]*(1-t); // if( once ) { // printf( "min = %g max = %g\n", minValue, maxValue ); // printf( "r = %g g = %g b = %g; t = %g\n", p[0], p[1], p[2], t ); // once = false; // } pr = binarySearchPixels( p[0], lutPixFloor, lutSize ); pg = binarySearchPixels( p[1], lutPixFloor, lutSize ); pb = binarySearchPixels( p[2], lutPixFloor, lutSize ); pixel = QColor( clamp( pr-1, 0, 255 ), clamp( pg-1, 0, 255 ), clamp( pb-1, 0, 255 ) ).rgb(); } } } else { pixel = QColor( clamp( pr-1, 0, 255 ), clamp( pg-1, 0, 255 ), clamp( pb-1, 0, 255 ) ).rgb(); } } else { pixel = QColor( clamp( pr-1, 0, 255 ), clamp( pg-1, 0, 255 ), clamp( pb-1, 0, 255 ) ).rgb(); } if( infNaNTreatment == INFNAN_MARK_AS_RED ) { if( !std::isfinite( (*R)(index) ) || !std::isfinite( (*G)(index) ) || !std::isfinite( (*B)(index) ) ) { // x is NaN or Inf pixel = QColor( 255, 0, 0 ).rgb(); } } if( negativeTreatment == NEGATIVE_MARK_AS_RED ) { if( (*R)(index)<0 || (*G)(index)<0 || (*B)(index)<0 ) { // x is negative pixel = QColor( 255, 0, 0 ).rgb(); } } } else { // Single channel int p = binarySearchPixels( (*R)(index), lutPixFloor, lutSize ); pixel = lutPixel[p]; if( infNaNTreatment == INFNAN_MARK_AS_RED && (p == 0 || p == LUTSIZE+1)) if( !std::isfinite( (*R)(index) ) ) { // x is NaN or Inf pixel = QColor( 255, 0, 0 ).rgb(); } } line[(int)imgCol] = pixel; if( expand ) { for( int ec = (int)imgCol + 1; ec < (int)(imgCol+imgDeltaCol); ec++ ) { line[ec] = pixel; } } } if( expand ) { for( int er = (int)(imgRow + 1); er < (int)(imgRow + imgDeltaRow); er++ ) { QRgb* eLine = (QRgb*)img->scanLine( er ); memcpy( eLine, line, sizeof( QRgb )*img->width() ); } } } } void PFSViewWidget::postUpdateMapping() { updateMappingRequested = true; update( 0, 0, image->width(), image->height() ); //updateContents( 0, 0, image->width(), image->height() ); } void PFSViewWidget::updateMapping() { assert( image != NULL ); QApplication::setOverrideCursor( Qt::WaitCursor ); if( visibleChannel == COLOR_CHANNELS ) { assert( workArea[0] != NULL && workArea[1] != NULL && workArea[2] != NULL ); mapFrameToImage( workArea[0], workArea[1], workArea[2], image, minValue, maxValue, clippingMethod, mappingMethod, infNaNTreatment, negativeTreatment ); } else { assert( workArea[0] != NULL ); mapFrameToImage( workArea[0], NULL, NULL, image, minValue, maxValue, clippingMethod, mappingMethod, infNaNTreatment, negativeTreatment ); } updateMappingRequested = false; QApplication::restoreOverrideCursor(); } void PFSViewWidget::updateZoom() { assert( pfsFrame != NULL ); QApplication::setOverrideCursor( Qt::WaitCursor ); if( visibleChannel == COLOR_CHANNELS ) { pfs::Channel *X, *Y, *Z; pfsFrame->getXYZChannels( X, Y, Z ); transformImageToWorkArea( workArea, zoom, true, X, Y, Z ); } else { pfs::Channel *X = pfsFrame->getChannel( visibleChannel ); transformImageToWorkArea( workArea, zoom, false, X ); } int zoomedWidth = max( workArea[0]->getCols(), (int)((float)(pfsFrame->getWidth())*zoom) ); int zoomedHeight = max( workArea[0]->getRows(), (int)((float)(pfsFrame->getHeight())*zoom) ); if( image != NULL ) delete image; image = new QImage( zoomedWidth, zoomedHeight, QImage::Format_RGB32 ); assert( image != NULL ); postUpdateMapping(); resize( zoomedWidth, zoomedHeight ); // resizeContents( zoomedWidth, zoomedHeight ); // updateContents( 0, 0, zoomedWidth, zoomedHeight ); update( 0, 0, zoomedWidth, zoomedHeight ); QApplication::restoreOverrideCursor(); } QSize PFSViewWidget::sizeHint() const { if( pfsFrame != NULL ) return QSize( (int)((float)pfsFrame->getWidth()*zoom), (int)((float)pfsFrame->getHeight()*zoom) ); // return QSize( (int)((float)pfsFrame->getWidth()*zoom) + 2 * frameWidth(), (int)((float)pfsFrame->getHeight()*zoom) + 2 * frameWidth() ); // return QSize( pfsFrame->getWidth() + 2 * frameWidth(), pfsFrame->getHeight() + 2 * frameWidth() ); else return QWidget::sizeHint(); } // ======================= Events =========================== void PFSViewWidget::setRGBClippingMethod( QAction *action ) { clippingMethod = (RGBClippingMethod)action->data().toUInt(); postUpdateMapping(); } void PFSViewWidget::setInfNaNTreatment( QAction *action ) { infNaNTreatment = (InfNaNTreatment)action->data().toUInt(); postUpdateMapping(); } void PFSViewWidget::setNegativeTreatment( QAction *action ) { negativeTreatment = (NegativeTreatment)action->data().toUInt(); postUpdateMapping(); } void PFSViewWidget::setLumMappingMethod( int method ) { mappingMethod = (LumMappingMethod)method; postUpdateMapping(); } void PFSViewWidget::zoomIn() { if( zoom >= 10 ) return; zoom *= 1.25f; fit_to_content_mode = false; updateZoom(); } void PFSViewWidget::zoomOut() { if( zoom <= 0.05 ) return; zoom *= 0.8f; fit_to_content_mode = false; updateZoom(); } void PFSViewWidget::zoomOriginal() { zoom = 1; fit_to_content_mode = false; updateZoom(); } void PFSViewWidget::setRangeWindow( float min, float max ) { minValue = min; maxValue = max; postUpdateMapping(); } // ===================== Mouse interaction ========================= void PFSViewWidget::mouseMoveEvent( QMouseEvent *mouseEvent ) { assert( pfsFrame != NULL ); setPointer( (int)((float)mouseEvent->x() / zoom), (int)((float)mouseEvent->y() / zoom) ); } void PFSViewWidget::setPointer( int x, int y ) { assert( pfsFrame != NULL ); if( x >= 0 ) { pointerValue.x = x; pointerValue.y = y; } if( pointerValue.x >= 0 && pointerValue.x < pfsFrame->getWidth() && pointerValue.y >= 0 && pointerValue.y < pfsFrame->getHeight() ) { if( visibleChannel == COLOR_CHANNELS ) { pfs::Channel *X, *Y, *Z; pfsFrame->getXYZChannels( X, Y, Z ); pointerValue.value[0] = (*X)( pointerValue.x, pointerValue.y ); pointerValue.value[1] = (*Y)( pointerValue.x, pointerValue.y ); pointerValue.value[2] = (*Z)( pointerValue.x, pointerValue.y ); pointerValue.valuesUsed = 3; pointerValue.valid = true; } else { pfs::Channel *X; X = pfsFrame->getChannel( visibleChannel ); pointerValue.value[0] = (*X)( pointerValue.x, pointerValue.y ); pointerValue.valuesUsed = 1; pointerValue.valid = true; } updatePointerValue(); } else { pointerValue.valid = false; updatePointerValue(); } } void PFSViewWidget::leaveEvent ( QEvent * ) { pointerValue.valid = false; updatePointerValue(); } const PointerValue &PFSViewWidget::getPointerValue() { return pointerValue; } // ===================== Data access ========================= const pfs::Array2D *PFSViewWidget::getPrimaryChannel() { assert( pfsFrame != NULL ); if( visibleChannel == COLOR_CHANNELS ) { pfs::Channel *X, *Y, *Z; pfsFrame->getXYZChannels( X, Y, Z ); return Y; } else { return pfsFrame->getChannel( visibleChannel ); } } const QList PFSViewWidget::getChannels() { assert( pfsFrame != NULL ); QList chArray; pfs::ChannelIterator *it = pfsFrame->getChannels(); it = pfsFrame->getChannels(); while( it->hasNext() ) { pfs::Channel *ch = it->getNext(); chArray.push_back(ch->getName()); } return chArray; } void PFSViewWidget::setVisibleChannel( const char *channelName ) { visibleChannelName = channelName; visibleChannel = channelName != NULL ? (const char*)visibleChannelName : COLOR_CHANNELS; updateZoom(); setPointer(); } const char *PFSViewWidget::getVisibleChannel() { return visibleChannel; } bool PFSViewWidget::hasColorChannels( pfs::Frame *frame ) { if( frame == NULL ) frame = pfsFrame; assert( frame != NULL ); pfs::Channel *X, *Y, *Z; frame->getXYZChannels( X, Y, Z ); return ( X != NULL ); } pfstools-2.2.0/src/matlab/0002775000701400070140000000000014105165615014113 5ustar rkm38rkm38pfstools-2.2.0/src/matlab/pfspopen.cpp0000664000701400070140000000177214105165615016456 0ustar rkm38rkm38#include "compatibility.h" #include #include #include "mex.h" #include "mex_utils.h" #define SCRIPT_NAME "pfspopen" extern void _main(); void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) { /* Check for proper number of arguments. */ if (nrhs != 2) mexErrMsgTxt("Expecting two parameters."); char *cmd_line = get_mex_string( prhs[0] ); char *mode = get_mex_string( prhs[1] ); FILE *in = popen( cmd_line, mode ); if( in == NULL ) { mexErrMsgTxt("popen has failed."); } int fd = fileno( in ); // int fd = dup( fileno( in ) ); // if( fclose( in ) != 0 ) // mexErrMsgTxt("fclose has failed."); plhs[0] = mxCreateNumericMatrix( 1, 2, mxDOUBLE_CLASS, mxREAL ); double *df_ptr = (double*)mxGetData( plhs[0] ); *df_ptr = fd; // Store the pointer as a 64-bit number, avoid type conversion *((unsigned long long*)(df_ptr+1)) = (unsigned long long)in; mxFree( cmd_line ); mxFree( mode ); return; } pfstools-2.2.0/src/matlab/pfsget.m0000664000701400070140000000041314105165615015555 0ustar rkm38rkm38% Read next frame from the pfs stream. See also help for pfsopen function. % % usage: new_pfs_struct = pfsget( pfs_struct ); % pfs_struct - the structure returned by pfsopen or pfsget % new_pfs_struct - new structure with new fields containing channels and tags pfstools-2.2.0/src/matlab/pfsclose.m0000664000701400070140000000022514105165615016104 0ustar rkm38rkm38% Close pfs stream. See also help for pfsopen function. % % usage: pfsclose( pfs_struct ); % pfs_struct - structure returned by pfsopen or pfsget pfstools-2.2.0/src/matlab/pfsopen.cpp0000664000701400070140000001073314105165615016273 0ustar rkm38rkm38/** * @brief Open pfs stream for reading or writing in MATLAB * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Rafal Mantiuk and Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Rafal Mantiuk, * * $Id: pfsopen.cpp,v 1.6 2010/06/05 18:24:24 rafm Exp $ */ #include "compatibility.h" #include #include #include #include #include #include "mex.h" #include "matrix.h" #include "mex_utils.h" #define SCRIPT_NAME "pfsopen" #define error mexErrMsgTxt // Because there is no O_BINARY under UNIX #ifndef O_BINARY #define O_BINARY 0 #endif #include void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) { /* Check for proper number of arguments. */ if (nrhs < 1 || nrhs > 3) error(SCRIPT_NAME ": Improper usage!"); if( !mxIsChar(prhs[0]) && !is_mex_fid( prhs[0] ) ) { error( SCRIPT_NAME ": expected file name or file descriptor as the first argument!"); } int width, height; bool writeMode = false; if( nrhs == 3 ) { if( !is_mex_scalar( prhs[1]) || !is_mex_scalar( prhs[2] ) ) { error( SCRIPT_NAME ": expected frame dimmensions as argument 2 and 3"); } height = (int)*mxGetPr( prhs[1] ); width = (int)*mxGetPr( prhs[2] ); writeMode = true; } if( nrhs == 2 ) { if( !mxIsNumeric( prhs[1] ) || mxGetM( prhs[1] ) != 1 || mxGetN( prhs[1] ) != 2) { error( SCRIPT_NAME ": expected 2-column matrix as argument 2"); } double *dim = mxGetPr( prhs[1] ); height = (int)dim[0]; width = (int)dim[1]; writeMode = true; } if( writeMode && (width < 1 || height < 1 || width > 65535 || height > 65535 ) ) { error( SCRIPT_NAME ": Illegal frame size"); } // Open file for reading or writing int fid; if( mxIsChar( prhs[0] ) ) { // File name given char *fileName = get_mex_string( prhs[0] ); if( writeMode ) { if( !strcmp( "stdout", fileName ) ) fid = 1; else { fid = open( fileName, O_CREAT | O_TRUNC | O_RDWR | O_BINARY, S_IREAD | S_IWRITE ); if( fid == -1 ) { error( SCRIPT_NAME ": cannot open file for writing!"); } } } else { if( !strcmp( "stdin", fileName ) ) fid = 0; else { fid = open( fileName, O_RDONLY | O_BINARY ); if( fid == -1 ) { error( SCRIPT_NAME ": cannot open file for reading!"); } } } mxFree( fileName ); } else { // File descriptor given double *p_fid = mxGetPr( prhs[0] ); fid = dup( (int)p_fid[0] ); if( fid == -1 ) error( SCRIPT_NAME ": Failed to use the file descriptor" ); } mxArray *pfs_stream = mxCreateStructMatrix( 1, 1, 0, NULL ); plhs[0] = pfs_stream; int fnum; fnum = mxAddField( pfs_stream, "FID" ); mxSetFieldByNumber( pfs_stream, 0, fnum, create_mex_double( fid ) ); fnum = mxAddField( pfs_stream, "MODE" ); if( writeMode ) mxSetFieldByNumber( pfs_stream, 0, fnum, create_mex_string("W") ); else mxSetFieldByNumber( pfs_stream, 0, fnum, create_mex_string("R") ); fnum = mxAddField( pfs_stream, "EOF" ); mxSetFieldByNumber( pfs_stream, 0, fnum, mxCreateLogicalScalar( false ) ); if( writeMode ) { fnum = mxAddField( pfs_stream, "columns" ); mxSetFieldByNumber( pfs_stream, 0, fnum, create_mex_double( width ) ); fnum = mxAddField( pfs_stream, "rows" ); mxSetFieldByNumber( pfs_stream, 0, fnum, create_mex_double( height ) ); fnum = mxAddField( pfs_stream, "channels" ); mxArray *channels = mxCreateStructMatrix( 1, 1, 0, NULL ); mxSetFieldByNumber( pfs_stream, 0, fnum, channels ); } } pfstools-2.2.0/src/matlab/pfs_read_rgb.m0000664000701400070140000000320414105165615016703 0ustar rkm38rkm38function [varargout] = pfs_read_rgb( fileName ) %PFS_READ_RGB Read image file and return R, G, and B color channels or a single 3D matrix. % % [R G B] = PFS_READ_RGB( file_name ) % IMG = PFS_READ_RGB( file_name) % % R, G, B - red, green and blue color channels, given as linear response % img - 3D matrix image, where img(:,:,1:3) represents red, blue and green % color channels % % PFS_READ_RGB accepts all formats recognized by the shell "pfsin" % command. % % See also: PFS_READ_IMAGE, PFS_READ_LUMINANCE, PFS_READ_XYZ, PFS_WRITE_IMAGE. % % Copyright 2009 Rafal Mantiuk %Check if file exists fid = fopen( fileName, 'rb' ); if( fid == -1 ) error( 'pfs_read_rgb: File "%s" does not exist', fileName ); end fclose( fid ); fid = pfspopen( sprintf( '%spfsin ''%s''%s', pfs_shell(), pfs_fix_path(fileName), pfs_shell( 1 ) ), 'r' ); pin = pfsopen( fid ); pin = pfsget( pin ); if( isfield( pin.channels, 'X' ) && isfield( pin.channels, 'Z' ) ) [R G B] = pfs_transform_colorspace( 'XYZ', pin.channels.X, pin.channels.Y, pin.channels.Z, 'RGB' ); elseif( isfield( pin.channels, 'Y' ) ) R = pin.channels.Y; G = pin.channels.Y; B = pin.channels.Y; else error( 'Color channels missing in the pfs frame' ); end if( nargout == 3 ) varargout{1} = R; varargout{2} = G; varargout{3} = B; elseif( nargout == 1 ) varargout{1}(:,:,1) = R; varargout{1}(:,:,2) = G; varargout{1}(:,:,3) = B; else error( 'pfs_read_rgb: wrong number of output arguments' ); end pfsclose( pin ); % TODO: Check why crashes on windows if ~ispc() pfspclose( fid ); end end pfstools-2.2.0/src/matlab/pfs_test_shell.m0000664000701400070140000000344714105165615017315 0ustar rkm38rkm38function pfs_test_shell() %PFS_TEST_SHELL run several test to check for common problems with pfstools %matlab interface. % % PFS_TEST_SHELL() % % The function displays test results and instruction what to do if the % test has failed. % % Copyright 2009 Rafal Mantiuk tmp_file = tempname; cmd = sprintf( '%secho "OK" | cat >''%s''%s', pfs_shell(), tmp_file, pfs_shell( 1 ) ); display( '===========================' ); display( ['Test 1: executing: ' cmd ] ); system( cmd ); display( ' If the lines below show a single line "OK", eveything is correct.' ); display( ' If the lines are empty, the shell most probably cannot be executed.' ); display( ' In such a case check error messages either in matlab command window' ); display( ' (Windows) or in the shell window from which you have started matlab (unix).' ); display( ' Then edit pfs_shell.m to fix these problems.' ); display( ' If the lines contain besides "OK" additional lines of text, fix shell startup' ); display( ' files (/etc/profile, ~/.bash_profile and others) so that no messages are' ); display( ' displayed when shell is started' ); display( '---- START ----' ); display_file( tmp_file ); display( '---- END ----' ); cmd = sprintf( '%swhich pfsin%s', pfs_shell(), pfs_shell( 1 ) ); display( '===========================' ); display( ['Test 2: executing: ' cmd ] ); [status result] = system( cmd ); if( status == 0 ) display( 'Successful.' ); else display( 'Shell failed to find pfstools in the PATH. Make sure that the PATH' ); display( ' includes directories with pfstools at shell startup.' ); display( 'Error message: ' ); display( result ); end end function display_file( file_name ) fid=fopen( file_name ); while 1 tline = fgetl(fid); if ~ischar(tline), break, end disp(tline) end fclose(fid); endpfstools-2.2.0/src/matlab/pfsclose.cpp0000664000701400070140000000376314105165615016444 0ustar rkm38rkm38/** * @brief Close pfs stream (MATLAB interface) * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Rafal Mantiuk and Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Rafal Mantiuk, * * $Id: pfsclose.cpp,v 1.5 2007/03/01 14:00:44 rdmantiuk Exp $ */ #include "compatibility.h" #include #include #include #include #include #include "mex.h" #include "mex_utils.h" #define SCRIPT_NAME "pfsclose" #define error mexErrMsgTxt void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) { /* Check for proper number of arguments. */ if( nrhs != 1 || !mxIsStruct( prhs[0] ) ) error(SCRIPT_NAME ": Improper usage!"); const mxArray *pfs_stream = prhs[0]; mxArray *f_fid = mxGetField( pfs_stream, 0, "FID" ); if( f_fid == NULL || !is_mex_scalar( f_fid ) ) { error( SCRIPT_NAME ": FH field missing in the structure or it has wrong type"); } int fid = (int)get_mex_double( f_fid ); int ret = close( fid ); if( ret != 0 ) error( SCRIPT_NAME ": Cannot close the file. Perhaps it is already closed."); } pfstools-2.2.0/src/matlab/mex_utils.cpp0000664000701400070140000001012414105165615016624 0ustar rkm38rkm38#include #include "mex_utils.h" mxArray *mxCreateLogicalScalar( bool val ) { mxArray *array = mxCreateNumericMatrix( 1, 1, mxDOUBLE_CLASS, mxREAL ); double *df_ptr = (double*)mxGetData( array ); *df_ptr = val; return array; } mxArray *create_mex_double( double val ) { mxArray *array = mxCreateNumericMatrix( 1, 1, mxDOUBLE_CLASS, mxREAL ); double *df_ptr = (double*)mxGetData( array ); *df_ptr = val; return array; } mxArray *create_mex_string( const char *str ) { char *buf = (char*)mxCalloc( strlen( str )+1, sizeof(char) ); strcpy( buf, str ); return mxCreateString( buf ); } char *get_mex_string( const mxArray *arg ) { /* Input must be a string. */ if (mxIsChar(arg) != 1) mexErrMsgTxt("Input must be a string."); /* Input must be a row vector. */ if (mxGetM(arg) != 1 && !(mxGetM(arg) == 0 && mxGetN(arg) == 0) ) mexErrMsgTxt("Input must be a row vector."); /* Get the length of the input string. */ int buflen = (mxGetM(arg) * mxGetN(arg)) + 1; /* Allocate memory for input and output strings. */ char *str = (char*)mxCalloc(buflen, sizeof(char)); /* Copy the string data from prhs[0] into a C string * input_buf. */ int status = mxGetString(arg, str, buflen); if (status != 0) mexWarnMsgTxt("Not enough space. String is truncated."); return str; } double get_mex_double( const mxArray *arg ) { double *data = mxGetPr( arg ); return data[0]; } float get_mex_float( const mxArray *arg ) { float* data = (float*)mxGetPr( arg ); return data[0]; } int set_mex_field( mxArray *arg, const char *field_name, mxArray *value ) { int fnum = mxGetFieldNumber( arg, field_name ); if( fnum == -1 ) { fnum = mxAddField( arg, field_name ); } mxSetFieldByNumber( arg, 0, fnum, value ); return fnum; } bool is_mex_scalar( const mxArray *arg ) { if( arg == NULL ) return false; return mxIsNumeric( arg ) && mxGetN( arg ) == 1 && mxGetM( arg ) == 1; } bool is_mex_fid( const mxArray *arg ) { if( arg == NULL ) return false; return mxIsNumeric( arg ) && (mxGetN( arg ) == 1 || mxGetN( arg ) == 2) && mxGetM( arg ) == 1; } void copy_mex_to_pfsarray( const mxArray *arg, pfs::Array2D *array ) { assert( mxIsSingle( arg ) || mxIsDouble( arg ) ); if ( mxIsDouble( arg ) ) copy_mex_to_pfsarray_T( arg, array ); else copy_mex_to_pfsarray_T( arg, array ); } void copy_mex_to_pfsarray( const mxArray *arg, pfs::Array2D *d1, pfs::Array2D *d2, pfs::Array2D *d3 ) { assert( mxIsSingle( arg ) || mxIsDouble( arg ) ); if ( mxIsDouble( arg ) ) copy_mex_to_pfsarray_T( arg, d1, d2, d3 ); else copy_mex_to_pfsarray_T( arg, d1, d2, d3 ); } void copy_pfsarray_to_mex( const pfs::Array2D *array, mxArray *arg ) { assert( mxIsSingle( arg ) || mxIsDouble( arg ) ); if ( mxIsDouble( arg ) ) copy_pfsarray_to_mex_T( array, arg ); else copy_pfsarray_to_mex_T( array, arg ); } void copy_pfsarray_to_mex( const pfs::Array2D *d1, const pfs::Array2D *d2, const pfs::Array2D *d3, mxArray *arg ) { assert( mxIsSingle( arg ) || mxIsDouble( arg ) ); if ( mxIsDouble( arg ) ) copy_pfsarray_to_mex_T( d1, d2, d3, arg ); else copy_pfsarray_to_mex_T( d1, d2, d3, arg ); } void copy_mex_to_pfschannel( const mxArray *arg, pfs::Channel *pfsChannel ) { assert( mxIsSingle( arg ) || mxIsDouble( arg ) ); if ( mxIsDouble( arg ) ) copy_mex_to_pfschannel_T( arg, pfsChannel ); else copy_mex_to_pfschannel_T( arg, pfsChannel ); } void copy_pfschannel_to_mex( const pfs::Channel *pfsChannel, mxArray *arg ) { assert( mxIsSingle( arg ) || mxIsDouble( arg ) ); if ( mxIsDouble( arg ) ) copy_pfschannel_to_mex_T( pfsChannel, arg ); else copy_pfschannel_to_mex_T( pfsChannel, arg ); } pfstools-2.2.0/src/matlab/Makefile.win320000664000701400070140000000172514105165615016517 0ustar rkm38rkm38#MEX=mex MEX=c:\progra~1\MATLAB\R2006b\bin\mex.bat #MEX="c:\matlab\bin\win32\mex.bat" MEXOPTIONS=COMPFLAGS^#"$$COMPFLAGS -GX" all: pfsclose.mexw32 pfsopen.mexw32 pfsput.mexw32 pfsget.mexw32 pfspopen.mexw32 \ pfspclose.mexw32 pfs_transform_colorspace.mexw32 pfsopen.mexw32: pfsopen.cpp mex_utils.cpp ../pfs/pfs.cpp $(MEX) $(MEXOPTIONS) -I..\pfs\ $** pfsclose.mexw32: pfsclose.cpp mex_utils.cpp $(MEX) $(MEXOPTIONS) -I..\pfs\ $** pfsput.mexw32: pfsput.cpp mex_utils.cpp ..\pfs\pfs.cpp $(MEX) $(MEXOPTIONS) -I..\pfs\ $** pfsget.mexw32: pfsget.cpp mex_utils.cpp ..\pfs\pfs.cpp $(MEX) $(MEXOPTIONS) -I..\pfs\ $** pfs_transform_colorspace.mexw32: pfs_transform_colorspace.cpp mex_utils.cpp ..\pfs\colorspace.cpp $(MEX) $(MEXOPTIONS) -I..\pfs\ $** pfspclose.mexw32: pfspclose.cpp mex_utils.cpp $(MEX) $(MEXOPTIONS) -I../../ -I../pfs $** pfspopen.mexw32: pfspopen.cpp mex_utils.cpp $(MEX) $(MEXOPTIONS) -I../../ -I../pfs $** pfstools-2.2.0/src/matlab/pfspclose.cpp0000664000701400070140000000160614105165615016616 0ustar rkm38rkm38#include "compatibility.h" #include #include #include #if defined(_WIN32) || defined(_WIN64) #include #include #else #include #endif #include "mex.h" #include "mex_utils.h" #define SCRIPT_NAME "pfspclose" void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) { /* Check for proper number of arguments. */ if (nrhs != 1 && !is_mex_scalar( prhs[0] ) ) mexErrMsgTxt( SCRIPT_NAME ": Expecting one parameter - file descriptor." ); if( mxGetN(prhs[0]) != 2 ) mexErrMsgTxt( SCRIPT_NAME ": File descriptor not created with pfspopen." ); double *p_fid = mxGetPr( prhs[0] ); // cast 64-bit number into a pointer FILE *fh = (FILE*)*((unsigned long long*)(p_fid+1)); if( pclose( fh ) == -1 ) mexErrMsgTxt( SCRIPT_NAME ": pclose has failed."); return; } pfstools-2.2.0/src/matlab/pfs_transform_colorspace.m0000664000701400070140000000264014105165615021366 0ustar rkm38rkm38%PFS_TRANSFORM_COLORSPACE Tranform between color spaces using pfs library. % % [C1 C2 C2] = PFS_TRANSFORM_COLORSPACE( inCSname, c1, c2, c3, outCSname ); % img_out = PFS_TRANSFORM_COLORSPACE( inCSname, img_in, outCSname ); % img_out = PFS_TRANSFORM_COLORSPACE( inCSname, c1, c2, c3, outCSname ); % [C1 C2 C2] = PFS_TRANSFORM_COLORSPACE( inCSname, img_in, outCSname ); % % inCSname - name of the input color space % c - matrix with n-th channel of input color space % C - matrix with n-th channel of output color space % img_in - input image given as 3D height/width/3 matrix % img_out - output image given as 3D height/width/3 matrix % outCSname - name of the output color space % % Recognized color space names: % 'XYZ' - CIE 1931 XYZ % 'RGB' - linear RGB with rec.709 primaries % 'sRGB' - sRGB colorspace, the maximum value is 1 % 'YUV' - Y is luminance, uv and u'v' chromacity coordinates of the CIE L'u'v' colorspace % 'Yxy' - CIE 1931 XYZ, but as chromatic coordinates % 'RGB2020' - linear RGB with rec.2020 primaries % 'PQYCbCr2020' - HDR10 MPEG color coding with the PQ transfer function. It assumes % 10,000 cd/m^2 peak luminance. The resulting values are not quantized and % are not rescaled to the valid range. % 'YCbCr709' - colorspace used for SDR MPEG coding. % Color space names are case insensitive. % % See also: PFS_READ_IMAGE, PFS_WRITE_IMAGE, PFSVIEW. % % Copyright 2009 Rafal Mantiuk pfstools-2.2.0/src/matlab/pfs_write_image.m0000664000701400070140000000373314105165615017441 0ustar rkm38rkm38function pfs_write_image( file_name, img, options ) %PFS_WRITE_IMAGE write an RGB, luminance or multichannel image to a file. % % PFS_WRITE_IMAGE( file_name, IMG [, options] ) % % Writes a luminance image if IMG is a 2D matrix, an RGB image if % size(IMG,3) == 3, and a multi-channel image otherwise. Each channel in a % multi-channel image has a name C1, C2, .., Cn. % % 'options' passes a string with additional options recognized by each of % the image format writers. For example '--compression=PXR24' modifies the % compression algorithm for OpenEXR files. See the manual pages of the % "pfsout*" shell commands for the list of supported options. % % The format of the file is recognized based in the file name extension: % .hdr for Radiance images, .exr for OpenEXR, .jpg for JPEG and .png for % PNG. See manual of "pfsout" shell command for the full list of the % supported formats. % % Currently only OpenEXR and PFS formats support multi-channel files. % % Pass single precission matrices for better performance. % % Example: % pfs_write_image( 'image.exr', cat( 3, R, G, B ) ); % % R, G, and B are 2D matrices with red, green and blue channel data. % % See also: PFS_WRITE_RGB, PFS_WRITE_LUMINANCE, PFS_WRITE_XYZ, % PFS_READ_IMAGE, PFSVIEW. % % Copyright 2009 Rafal Mantiuk img_sz = size( img ); dims = length( img_sz ); if( dims > 3 ) error( 'image matrix has too many dimenstions' ); end if( ~exist( 'options', 'var' ) ) options = ''; end fid = pfspopen( sprintf( '%spfsout ''%s'' %s%s', pfs_shell(), pfs_fix_path(file_name), options, pfs_shell(1) ), 'w' ); pfs = pfsopen( fid, [img_sz(1) img_sz(2)] ); if( dims == 2 || img_sz(3) == 1 ) pfs.channels.Y = single(img); elseif( dims == 3 && img_sz(3) == 3 ) [pfs.channels.X pfs.channels.Y pfs.channels.Z] = pfs_transform_colorspace( 'RGB', img, 'XYZ' ); else for k=1:img_sz(3) pfs.channels.(sprintf('C%d', k)) = single(img(:,:,k)); end end pfsput( pfs ); pfsclose( pfs ); pfspclose( fid ); end pfstools-2.2.0/src/matlab/pfsget.cpp0000664000701400070140000001315414105165615016111 0ustar rkm38rkm38/** * @brief Read frame from pfs stream in Matlab * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Rafal Mantiuk and Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Rafal Mantiuk, * * $Id: pfsget.cpp,v 1.5 2008/05/06 17:11:35 rafm Exp $ */ #include "compatibility.h" #include #include #include #include #include "mex.h" #include "mex_utils.h" #define SCRIPT_NAME "pfsget" #define error mexErrMsgTxt #include void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) { /* Check for proper number of arguments. */ if( nrhs != 1 || !mxIsStruct( prhs[0] ) ) error(SCRIPT_NAME ": Improper usage!"); const mxArray *pfs_stream_in = prhs[0]; mxArray *pfs_stream = mxCreateStructMatrix( 1, 1, 0, NULL ); mxArray *f_fid = mxGetField( pfs_stream_in, 0, "FID" ); if( f_fid == NULL || !is_mex_scalar( f_fid ) ) { error( SCRIPT_NAME ": FH field missing in the structure or it has wrong type"); } int fid = (int)get_mex_double( f_fid ); set_mex_field( pfs_stream, "FID", mxDuplicateArray( f_fid ) ); // Check mode { mxArray *f_mode = mxGetField( pfs_stream_in, 0, "MODE" ); if( f_mode == NULL ) { error( SCRIPT_NAME ": MODE field missing in the structure or it has wrong type"); } if( strcmp( "R", get_mex_string( f_mode ) ) ) { error( SCRIPT_NAME ": Can not read from the stream that is open for writing." ); } set_mex_field( pfs_stream, "MODE", mxDuplicateArray( f_mode ) ); } //mxRemoveField( pfs_stream, mxGetFieldNumber( pfs_stream, "channels" ) ); //mxRemoveField( pfs_stream, mxGetFieldNumber( pfs_stream, "tags" ) ); //mxRemoveField( pfs_stream, mxGetFieldNumber( pfs_stream, "channelTags" ) ); FILE *fh = fdopen( dup( fid ), "r" ); if( fh == NULL ) error( SCRIPT_NAME ": Cannot open file for reading" ); try { pfs::DOMIO ctx; pfs::Frame *frame = ctx.readFrame( fh ); if( frame == NULL ) { // No more frames set_mex_field( pfs_stream, "EOF", mxCreateLogicalScalar( true ) ); } else { // Not EOF // Set width and height { set_mex_field( pfs_stream, "columns", create_mex_double( frame->getWidth() ) ); set_mex_field( pfs_stream, "rows", create_mex_double( frame->getHeight() ) ); } // Add channels as matrices to pfs stream struct { mxArray *channels = mxCreateStructMatrix( 1, 1, 0, NULL ); pfs::ChannelIteratorPtr cit( frame->getChannelIterator() ); while( cit->hasNext() ) { pfs::Channel *ch = cit->getNext(); mxArray *mat = mxCreateNumericMatrix( ch->getRows(), ch->getCols(), mxDEFAULT_ARRAY_CLASS, mxREAL ); copy_pfschannel_to_mex( ch, mat ); set_mex_field( channels, ch->getName(), mat ); } set_mex_field( pfs_stream, "channels", channels ); } //Add tags (only frame tags, no channel tags) { mxArray *tags = mxCreateStructMatrix( 1, 1, 0, NULL ); pfs::TagIteratorPtr it( frame->getTags()->getIterator() ); while( it->hasNext() ) { const char *tagName = it->getNext(); mxArray *tag_value = create_mex_string( frame->getTags()->getString( tagName ) ); set_mex_field( tags, tagName, tag_value ); } set_mex_field( pfs_stream, "tags", tags ); } //Add tags /* { Octave_map tags; pfs::TagIteratorPtr it( frame->getTags()->getIterator() ); while( it->hasNext() ) { const char *tagName = it->getNext(); tags.assign( tagName, octave_value( frame->getTags()->getString( tagName )) ); } pfsStream.assign( "tags", tags ); Octave_map channelTagList; //Copy all channel tags pfs::ChannelIteratorPtr cit( frame->getChannelIterator() ); while( cit->hasNext() ) { pfs::Channel *ch = cit->getNext(); Octave_map channelTags; pfs::TagIteratorPtr tit( ch->getTags()->getIterator() ); while( tit->hasNext() ) { const char *tagName = tit->getNext(); channelTags.assign( tagName, octave_value(ch->getTags()->getString( tagName )) ); } channelTagList.assign( ch->getName(), channelTags ); } pfsStream.assign( "channelTags", channelTagList ); }*/ } ctx.freeFrame( frame ); plhs[0] = pfs_stream; } catch( pfs::Exception ex ) { char error_message[100]; sprintf( error_message, "%s: %s", SCRIPT_NAME, ex.getMessage() ); error( error_message ); } if( fh != NULL ) fclose( fh ); } pfstools-2.2.0/src/matlab/CMakeLists.txt0000664000701400070140000001166414105165615016661 0ustar rkm38rkm38 # PROJECT_BINARY_DIR contains config.h and is thus needed include_directories ("${PROJECT_SOURCE_DIR}/src/pfs" "${PROJECT_BINARY_DIR}") if( CYGWIN ) # Cygwin normally does not require getopt but matlab sourves will be compiled with an external matlab # compiler, most probably Visual Studio include_directories ("${PROJECT_SOURCE_DIR}/src/getopt") set( SCRIPT_EXT ".bat" ) else( CYGWIN ) set( SCRIPT_EXT "" ) endif( CYGWIN ) #include_directories ("${PROJECT_BINARY_DIR}/" "${PROJECT_SOURCE_DIR}/src/pfs" ${MATLAB_INCLUDE_DIR}) #link_directories("${PROJECT_SOURCE_DIR}/src/pfs") execute_process(COMMAND ${MATLAB_ROOT}/bin/mexext${SCRIPT_EXT} OUTPUT_VARIABLE MEX_EXT OUTPUT_STRIP_TRAILING_WHITESPACE) message( "Matlab: mexext: ${MEX_EXT}" ) set(SRCMEX pfsclose.cpp pfsopen.cpp pfspopen.cpp pfsput.cpp pfsget.cpp pfs_transform_colorspace.cpp pfspclose.cpp) set(SRCM Contents.m pfs_read_image.m pfs_read_luminance.m pfs_read_rgb.m pfs_read_xyz.m pfs_shell.m pfs_test_shell.m pfs_write_image.m pfs_write_luminance.m pfs_write_rgb.m pfs_write_xyz.m pfsview.m pfs_fix_path.m pfs_exec.m) #cmake_policy(SET CMP0018 OLD) #cmake_policy(SET CMP0042 OLD) This policy is not recognized in cmake version 2.8.12.2 SET( CMAKE_CXX_COMPILER ${MATLAB_MEX} ) SET( CMAKE_C_COMPILER ${MATLAB_MEX} ) # Suffix and Prefix of the output target file SET( CMAKE_SHARED_LIBRARY_SUFFIX ".${MEX_EXT}" ) # set suffix to .mexa64 SET( CMAKE_SHARED_LIBRARY_PREFIX ) # remove the "lib" prefix # Variables controlling the build-phrase SET( CMAKE_CXX_FLAGS "-cxx -largeArrayDims" ) #SET( CMAKE_CXX_FLAGS "-cxx -largeArrayDims CXXFLAGS='$$CXXFLAGS -std=c++11'" ) #SET( CMAKE_SHARED_LIBRARY_CXX_FLAGS ) # remove the -fPIC option. mex does not accept the "-fPIC" option get_property(inc_dirs DIRECTORY PROPERTY INCLUDE_DIRECTORIES) set( INC_FLAGS "" ) foreach( IDIR ${inc_dirs} ) set( INC_FLAGS "${INC_FLAGS} -I${IDIR}" ) endforeach( IDIR ) if( CYGWIN ) # This is ugly but necessary to work around CMake limitations # The compiled objects need to be copied to ensure that the dependency check works # Linker expects the obejcts to be in the so we need to have them there as well SET( CMAKE_CXX_COMPILE_OBJECT " ${INC_FLAGS} -outdir -c ; cp /$$(basename .cpp ).obj " ) else( CYGWIN ) if( MEXGCC ) set( GCC_COMMAND "GCC=${MEXGCC}" ) MESSAGE( STATUS "Using gcc for MEX: ${MEXGCC}.\n" ) else( MEXGCC ) set( GCC_COMMAND "" ) endif( MEXGCC ) SET( CMAKE_CXX_COMPILE_OBJECT " ${GCC_COMMAND} ${INC_FLAGS} -outdir -c ; mv /$$(basename .cpp ).o " # " ${INC_FLAGS} -outdir -c ; mv /$$(basename .cpp ).o " # " ${INC_FLAGS} -outdir -c " ) endif( CYGWIN ) # Variables controlling the linking-phase SET( CMAKE_SHARED_LIBRARY_CREATE_CXX_FLAGS ) # remove -shared options. mex does not accept the "-shared" option SET( CMAKE_CXX_CREATE_SHARED_LIBRARY " ${GCC_COMMAND} -cxx -output " # " GCC='/usr/bin/gcc-4.7' -cxx -output LDFLAGS='$LDFLAGS -static-libstdc++'" ) # #SET( CMAKE_SKIP_RPATH TRUE ) # Variables controlling the installation RPATH SET( CMAKE_INSTALL_RPATH "\$ORIGIN" ) # CMake will reset RPATH at the installation phase, so we need to specify CMAKE_INSTALL_RPATH SET( CMAKE_SKIP_RPATH TRUE ) MESSAGE( STATUS "mex.cmake is loaded.\n" ) # Since Matlab tends to use ancient version of stdc++, dynamic linkimg with pfs library # causes heaps of problems. It is better to statically add required files. SET( PFS_LIB_SRC ${PROJECT_SOURCE_DIR}/src/pfs/pfs.cpp ${PROJECT_SOURCE_DIR}/src/pfs/colorspace.cpp ) ## To add an mex file, you can: # ADD_LIBRARY( myprogram SHARED myprogram.cxx ) ## Make sure you specify it as SHARED, and make sure you use the cxx extension. add_definitions(-DMATLAB_MEX_FILE) foreach(SRC ${SRCMEX}) get_filename_component(MEXNAME ${SRC} NAME_WE) add_library(${MEXNAME} SHARED ${SRC} mex_utils.cpp ${PFS_LIB_SRC} ${MEXDEFFILE}) # target_link_libraries(${MEXNAME} pfs ${MATLAB_LIBRARIES}) # target_link_libraries(${MEXNAME} pfs ) # set_target_properties(${MEXNAME} PROPERTIES PREFIX "" SUFFIX #".${MEX_EXT}") set_property(TARGET ${MEXNAME} PROPERTY POSITION_INDEPENDENT_CODE OFF) # remove the -fPIC option. mex does not accept the "-fPIC" option install (TARGETS ${MEXNAME} DESTINATION ${MATLAB_DEST_DIR}) install (FILES ${MEXNAME}.m DESTINATION ${MATLAB_DEST_DIR}) endforeach(SRC) foreach(SRC ${SRCM}) install (FILES ${SRC} DESTINATION ${MATLAB_DEST_DIR}) endforeach(SRC) pfstools-2.2.0/src/matlab/mex_utils.h0000664000701400070140000000716114105165615016300 0ustar rkm38rkm38#ifndef MEX_UTILS_H #define MEX_UTILS_H #include "mex.h" #include #include namespace pfs { class Array2D; } #define mxDEFAULT_ARRAY_CLASS mxSINGLE_CLASS mxArray *create_mex_double( double val ); mxArray *create_mex_string( const char *str ); char *get_mex_string( const mxArray *arg ); double get_mex_double( const mxArray *arg ); float get_mex_float( const mxArray *arg ); bool is_mex_scalar( const mxArray *arg ); bool is_mex_fid( const mxArray *arg ); int set_mex_field( mxArray *arg, const char *field_name, mxArray *value ); void copy_mex_to_pfsarray( const mxArray *arg, pfs::Array2D *array ); void copy_pfsarray_to_mex( const pfs::Array2D *array, mxArray *arg ); void copy_mex_to_pfsarray( const mxArray *arg, pfs::Array2D *d1, pfs::Array2D *d2, pfs::Array2D *d3 ); void copy_pfsarray_to_mex( const pfs::Array2D *d1, const pfs::Array2D *d2, const pfs::Array2D *d3, mxArray *arg ); void copy_mex_to_pfschannel( const mxArray *arg, pfs::Channel *pfsChannel ); void copy_pfschannel_to_mex( const pfs::Channel *pfsChannel, mxArray *arg ); mxArray *mxCreateLogicalScalar( bool value ); template void copy_mex_to_pfsarray_T( const mxArray *arg, pfs::Array2D *array ) { assert( array->getCols() == mxGetN( arg ) && array->getRows() == mxGetM( arg ) ); const T *data = (T *)mxGetPr( arg ); for( int c = 0; c < array->getCols(); c++ ) for( int r = 0; r < array->getRows(); r++ ) { (*array)(c,r) = (float)*(data++); } } template void copy_mex_to_pfsarray_T( const mxArray *arg, pfs::Array2D *d1, pfs::Array2D *d2, pfs::Array2D *d3 ) { // *3 is needed as N return combined 2 and 3rd dimensions. // assert( d1->getCols()*3 == mxGetN( arg ) && d1->getRows() == mxGetM( arg ) ); T *o1 = (T *)mxGetPr( arg ); const long sdsize = d1->getCols()*d1->getRows(); T *o2 = o1 + sdsize; T *o3 = o2 + sdsize; for( int c = 0; c < d1->getCols(); c++ ) for( int r = 0; r < d1->getRows(); r++ ) { (*d1)(c,r) = (float)*(o1++); (*d2)(c,r) = (float)*(o2++); (*d3)(c,r) = (float)*(o3++); } } template void copy_pfsarray_to_mex_T( const pfs::Array2D *array, mxArray *arg ) { assert( array->getCols() == mxGetN( arg ) && array->getRows() == mxGetM( arg ) ); T *data = (T *)mxGetPr( arg ); for( int c = 0; c < array->getCols(); c++ ) for( int r = 0; r < array->getRows(); r++ ) { *(data++) = (*array)(c,r); } } template void copy_pfsarray_to_mex_T( const pfs::Array2D *d1, const pfs::Array2D *d2, const pfs::Array2D *d3, mxArray *arg ) { // assert( d1->getCols() == mxGetN( arg ) && d1->getRows() == mxGetM( arg ) ); T *o1 = (T *)mxGetPr( arg ); const long sdsize = d1->getCols()*d1->getRows(); T *o2 = o1 + sdsize; T *o3 = o2 + sdsize; for( int c = 0; c < d1->getCols(); c++ ) for( int r = 0; r < d1->getRows(); r++ ) { *(o1++) = (*d1)(c,r); *(o2++) = (*d2)(c,r); *(o3++) = (*d3)(c,r); } } template void copy_mex_to_pfschannel_T( const mxArray *arg, pfs::Channel *pfsChannel ) { int index = 0; T *data = (T *)mxGetPr( arg ); for( int c = 0; c < pfsChannel->getCols(); c++ ) for( int r = 0; r < pfsChannel->getRows(); r++ ) { (*pfsChannel)(c,r) = *(data++); } } template void copy_pfschannel_to_mex_T( const pfs::Channel *pfsChannel, mxArray *arg ) { int index = 0; T *data = (T *)mxGetPr( arg ); for( int c = 0; c < pfsChannel->getCols(); c++ ) for( int r = 0; r < pfsChannel->getRows(); r++ ) { *(data++) = (*pfsChannel)(c,r); } } #endif pfstools-2.2.0/src/matlab/Contents.m0000664000701400070140000000257514105165615016075 0ustar rkm38rkm38% pfstools - matlab interface % % This is an entry page for pfstools matlab documentation. % % ----------------------------------------------------------------- % The most frequently used functions: % % PFS_READ_IMAGE - read image from file % PFS_WRITE_IMAGE - write image to file % PFSVIEW - view matrices as images using pfsview % % ----------------------------------------------------------------- % More specialized functions: % % PFS_TRANSFORM_COLORSPACE - transform between color spaces % PFS_READ_LUMINANCE - read luminance image from file % PFS_READ_RGB - read RGB image from file % PFS_READ_XYZ - read XYZ image from file % PFS_WRITE_LUMINANCE - write luminance image to file % PFS_WRITE_RGB - write RGB image to file % PFS_WRITE_XYZ - write XYZ image to file % PFS_TEST_SHELL - diagnoze for possible problems if pfstools interface is not working % % ----------------------------------------------------------------- % Low-level interface, probably you do not need this: % % PFSOPEN - open pfs-stream for reading/writing % PFSPOPEN - execute shell command and acquire its input / output stream % PFSCLOSE - close pfs-stream % PFSPCLOSE - close stream created with PFSPOPEN % PFSGET - read one frame from pfs-stream % PFSPUT - write one frame to pfs-stream % PFS_SHELL - wrapper for calling pfstools shell programs from matlab % % ----------------------------------------------------------------- pfstools-2.2.0/src/matlab/pfs_write_rgb.m0000664000701400070140000000301014105165615017115 0ustar rkm38rkm38function pfs_write_rgb( fileName, varargin ) %PFS_WRITE_RGB write an RGB image file. % % PFS_WRITE_RGB( file_name, R, G, B ) % PFS_WRITE_RGB( file_name, img ) % % R, G, B - red, green and blue color channels, given as linear response % img - 3D matrix image, where img(:,:,1:3) represents red, blue and green % color channels % % The format of the file is recognized based in the file name extension: % .hdr for Radiance images, .exr for OpenEXR, .jpg for JPEG and .png for % PNG. See manual of "pfsout" shell command for the full list of the % supported formats. % % See also: PFS_WRITE_IMAGE, PFS_WRITE_LUMINANCE, PFS_WRITE_XYZ, % PFS_READ_IMAGE. % % Copyright 2009 Rafal Mantiuk if( nargin == 4 ) if( ~isnumeric(varargin{1}) || ~isnumeric(varargin{2}) || ~isnumeric(varargin{1}) ) error( 'pfs_write_rgb: matrices of the equal size expected as an arguments' ); end [X Y Z] = pfs_transform_colorspace( 'RGB', varargin{1}, varargin{2}, varargin{3}, 'XYZ' ); elseif( nargin == 2 && ndims(varargin{1}) == 3 ) [X Y Z] = pfs_transform_colorspace( 'RGB', varargin{1}(:,:,1), varargin{1}(:,:,2), varargin{1}(:,:,3), 'XYZ' ); else error( 'pfs_write_rgb: improper usage' ); end %cmd = sprintf( '%spfsout %s%s', pfs_shell(), fileName, pfs_shell(1) ) fid = pfspopen( sprintf( '%spfsout ''%s''%s', pfs_shell(), pfs_fix_path(fileName), pfs_shell(1) ), 'w' ); pfs = pfsopen( fid, size( X ) ); pfs.channels.X = X; pfs.channels.Y = Y; pfs.channels.Z = Z; pfsput( pfs ); pfsclose( pfs ); pfspclose( fid ); end pfstools-2.2.0/src/matlab/pfs_write_luminance.m0000664000701400070140000000161014105165615020322 0ustar rkm38rkm38function pfs_write_luminance( fileName, img ) %PFS_WRITE_LUMINANCE writes an image file % % PFS_WRITE_LUMINANCE ( file_name, img ) % % img - 2D matrix representing luminance values % % The format of the file is recognized based in the file name extension: % .hdr for Radiance images, .exr for OpenEXR, .jpg for JPEG and .png for % PNG. See manual of "pfsout" shell command for the full list of the % supported formats. % % See also: PFS_WRITE_IMAGE, PFS_WRITE_RGB, PFS_WRITE_XYZ, % PFS_READ_IMAGE. % % Copyright 2009 Rafal Mantiuk if( nargin ~= 2 ) error( 'pfs_write_luminance: improper usage' ); end %cmd = sprintf( '%spfsout %s%s', pfs_shell(), fileName, pfs_shell(1) ) fid = pfspopen( sprintf( '%spfsout ''%s''%s', pfs_shell(), pfs_fix_path(fileName), pfs_shell(1) ), 'w' ); pfs = pfsopen( fid, size( img ) ); pfs.channels.Y = img; pfsput( pfs ); pfsclose( pfs ); pfspclose( fid ); end pfstools-2.2.0/src/matlab/compile_win32.bat0000664000701400070140000000003714105165615017253 0ustar rkm38rkm38 nmake -f Makefile.win32 pfstools-2.2.0/src/matlab/pfs_exec.m0000775000701400070140000000334014105165615016066 0ustar rkm38rkm38function [status, cmdout] = pfs_exec( cmd_line ) % Execute commnand line in the native environment if ispc() [work_dir, cygwin_home] = pfs_fix_path( pwd() ); % Replace all DOS paths with Unix paths for cygwin cl = strsplit( cmd_line ); cmd_line = []; for kk=1:length(cl) if( ~isempty(strfind(cl{kk}, '\')) ) % If a string contains \, it is assumed to be a path cmd_line = cat( 2, cmd_line, ' ', pfs_fix_path( cl{kk} ) ); else cmd_line = cat( 2, cmd_line, ' ', cl{kk} ); end end % This is put at the beginning of the shell command cmd = [ cygwin_home '\bin\bash -l -c "cd ''' work_dir ''';' cmd_line '"']; else work_dir = pwd(); % It is necessary to set all ENV variables before invoking % pfstools commands. '/bin/bash' may need to be replaced with the % shell you are using. % This will remove all references to matlab libraries from the % LD_LIBRARY_PATH. pfstools usually do not work with matlab version % of the standard libraries set_ld_path='export LD_LIBRARY_PATH=`echo $LD_LIBRARY_PATH | sed "y/:/\n/" | grep -v "matlab" | sed ":beg;N;s/\n/:/;t beg;"`'; if( strcmp( computer, 'MACI64' ) || strcmp( computer, 'MACI32' ) ) set_ld_path=cat( 2, set_ld_path, '; export DYLD_FRAMEWORK_PATH=`echo $DYLD_FRAMEWORK_PATH | sed "y/:/\n/" | grep -v "matlab" | sed ":beg;N;s/\n/:/;t beg;"`' ); end % Start shell, cd to the working directory and remove matlab % libraries cmd = [ '/bin/bash -l -c "cd ''' work_dir '''; ' set_ld_path '; ' cmd_line '"' ]; end if nargout == 2 [status, cmdout] = system( cmd ); else status = system( cmd ); end end pfstools-2.2.0/src/matlab/pfspclose.m0000664000701400070140000000016414105165615016266 0ustar rkm38rkm38% Close fifo pipe. Used internally by pfstools. % % pfspclose( fid ) % % fid - file descriptor returned by pfspopen pfstools-2.2.0/src/matlab/pfs_read_luminance.m0000664000701400070140000000162414105165615020110 0ustar rkm38rkm38function Y = pfs_read_luminance( fileName ) %PFS_READ_LUMINANCE read image file and return luminance channel Y. % % Y = PFS_READ_LUMINANCE( file_name ) % % PFS_READ_LUMINANCE accepts all formats recognized by the shell "pfsin" % command. % % See also: PFS_READ_IMAGE, PFS_READ_LUMINANCE, PFS_READ_XYZ, PFS_WRITE_IMAGE. % % Copyright 2009 Rafal Mantiuk % Check if file exists fid = fopen( fileName, 'r' ); if( fid == -1 ) error( 'pfs_read_luminance: File "%s" does not exist', fileName ); end fclose( fid ); % try cmd = sprintf( '%spfsin ''%s''%s', pfs_shell(), pfs_fix_path(fileName), pfs_shell( 1 ) ); fid = pfspopen( cmd, 'r' ); pin = pfsopen( fid ); pin = pfsget( pin ); Y = pin.channels.Y; pfsclose( pin ); % TODO: Check why crashes on windows if ~ispc() pfspclose( fid ); end % catch % pfsclose( pin ); % pfspclose( fid ); % end endpfstools-2.2.0/src/matlab/pfsopen.m0000664000701400070140000000311114105165615015735 0ustar rkm38rkm38% Open pfs stream for reading or writing. pfs is an interchange format for high dynamic range images (see http://pfstools.sourceforge.net). % % usage: pfs_struct = pfsopen( fileName ); % pfs_struct = pfsopen( fileName, rows, columns ); % pfs_struct = pfsopen( fileName, [ rows columns ] ); % pfs_struct = pfsopen( fid, ... ); % % fileName - name of the file to read or write. \"stdin\" or \"stdout\" for standard input and output % rows - height of images to write % columns - width of images to write % % The first usage of pfsopen opens pfs stream for reading, the second and % the third for writing. pfsopen also accepts file descriptor returned from % pfspopen, which can be used instead of a file name (the fourth usage). Use % pfsget or pfsput to read or write frames or single images. You must close % pfs stream with pfsclose. The stream will not be closed when pfs_struct % is deleted (for example with 'clear pfs_struct'). % % pfs_struct is a structure that contains the following fields: % EOF - set to 1 if there are no more frames; 0 otherwise % FH - file handle of the file. For internal pruposes, do not use % MODE - file open mode: R - for reading, W - for writing % columns, rows - dimensions of each channel in the stream % channels - structure that contains channels represented as real matrices % tags - structure that contains tags represented as strings % channelTags - structure that contains a structure for each channel, % which contains tags. The format of the latter structure is the same as % for 'tags' field. pfstools-2.2.0/src/matlab/pfs_read_xyz.m0000664000701400070140000000205414105165615016765 0ustar rkm38rkm38function varargout = pfs_read_xyz( fileName ) %PFS_READ_XYZ read image file and return X, Y, and Z color channels. % % [X Y Z] = PFS_READ_XYZ( file_name ) % IMG = PFS_READ_XYZ( file_name ) % % PFS_READ_XYZ accepts all formats recognized by the shell "pfsin" % command. % % See also: PFS_READ_IMAGE, PFS_READ_LUMINANCE, PFS_READ_XYZ, PFS_WRITE_IMAGE. % % Copyright 2009 Rafal Mantiuk % Check if file exists fid = fopen( fileName, 'rb' ); if( fid == -1 ) error( 'pfs_read_xyz: File "%s" does not exist', fileName ); end fclose( fid ); fid = pfspopen( sprintf( '%spfsin ''%s''%s', pfs_shell(), pfs_fix_path(fileName), pfs_shell( 1 ) ), 'r' ); pin = pfsopen( fid ); pin = pfsget( pin ); pfsclose( pin ); pfspclose( fid ); if( nargout == 3 ) varargout{1} = pin.channels.X; varargout{2} = pin.channels.Y; varargout{3} = pin.channels.Z; elseif( nargout == 1 ) varargout{1} = cat( 3, pin.channels.X, pin.channels.Y, pin.channels.Z ); else error( 'Wrong number of output parameters' ); end end pfstools-2.2.0/src/matlab/compatibility.h0000664000701400070140000000321514105165615017134 0ustar rkm38rkm38/** * @brief Compatibility header file * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Rafal Mantiuk and Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Grzegorz Krawczyk, * * $Id: compatibility.h,v 1.1 2007/03/01 14:10:57 rdmantiuk Exp $ */ #ifndef COMPATIBILITY_H #define COMPATIBILITY_H #ifdef _MSC_VER #include #define popen _popen #define dup _dup //#define fileno _fileno #define fdopen _fdopen #define open _open #define pclose _pclose #define O_CREAT _O_CREAT #define O_TRUNC _O_TRUNC #define O_RDWR _O_RDWR #define S_IREAD _S_IREAD #define S_IWRITE _S_IWRITE #define O_BINARY _O_BINARY #endif #ifdef __linux__ #include #define O_BINARY 0 //#define stricmp strcasecmp #endif //#define DEBUG_STR if(1); else cerr #endif pfstools-2.2.0/src/matlab/pfs_shell.m0000664000701400070140000000321214105165615016244 0ustar rkm38rkm38function cmd = pfs_shell( suffix ) % Returns command line that starts shell. Internal, do not use. if ispc() % -i option is needed to make sure that .bash_rc is executed and thus % DISPLAY and other environment variables are set if( exist( 'suffix', 'var' ) ) % This is put at the end of the shell command cmd = '"'; else [work_dir, cygwin_home] = pfs_fix_path( pwd() ); % This is put at the beginning of the shell command cmd = sprintf('%s\\bin\\bash -l -c "cd ''%s'';', cygwin_home, work_dir); end else if( ~exist( 'suffix', 'var' ) ) work_dir = strrep(pwd(),'\','/'); % It is necessary to set all ENV variables before invoking % pfstools commands. '/bin/bash' may need to be replaced with the % shell you are using. % This will remove all references to matlab libraries from the % LD_LIBRARY_PATH. pfstools usually do not work with matlab version % of the standard libraries set_ld_path='export LD_LIBRARY_PATH=`echo $LD_LIBRARY_PATH | sed "y/:/\n/" | grep --ignore-case -v "matlab" | sed ":beg;N;s/\n/:/;t beg;"`'; if( strcmp( computer, 'MACI64' ) || strcmp( computer, 'MACI32' ) ) set_ld_path=cat( 2, set_ld_path, '; export DYLD_FRAMEWORK_PATH=`echo $DYLD_FRAMEWORK_PATH | sed "y/:/\n/" | grep --ignore-case -v "matlab" | sed ":beg;N;s/\n/:/;t beg;"`' ); end % Start shell, cd to the working directory and remove matlab % libraries cmd = sprintf('/bin/bash -l -c ''cd "%s"; %s;', work_dir, set_ld_path ); else cmd=''''; end end end pfstools-2.2.0/src/matlab/pfs_write_xyz.m0000664000701400070140000000300314105165615017177 0ustar rkm38rkm38function pfs_write_xyz( fileName, varargin ) %PFS_WRITE_XYZ write an XYZ image file. % % PFS_WRITE_XYZ( file_name, X, Y, Z ) % PFS_WRITE_XYZ( file_name, img ) % % X, Y, Z - XYZ color channels, given as linear response % img - 3D matrix image, where img(:,:,1:3) represents XYZ color channels % % The format of the file is recognized based in the file name extension: % .hdr for Radiance images, .exr for OpenEXR, .jpg for JPEG and .png for % PNG. See manual of "pfsout" shell command for the full list of the % supported formats. % % See also: PFS_WRITE_IMAGE, PFS_WRITE_LUMINANCE, PFS_WRITE_RGB, % PFS_READ_IMAGE. % % Copyright 2009 Rafal Mantiuk %cmd = sprintf( '%spfsout %s%s', pfs_shell(), fileName, pfs_shell(1) ) fid = pfspopen( sprintf( '%spfsout ''%s''%s', pfs_shell(), pfs_fix_path(fileName), pfs_shell(1) ), 'w' ); pfs = pfsopen( fid, [size( varargin{1}, 1 ) size( varargin{1}, 2 )] ); if( nargin == 4 ) if( ~isnumeric(varargin{1}) || ~isnumeric(varargin{2}) || ~isnumeric(varargin{1}) ) error( 'pfs_write_xyz: matrices of the equal size expected as an arguments' ); end pfs.channels.X = single(varargin{1}); pfs.channels.Y = single(varargin{2}); pfs.channels.Z = single(varargin{3}); elseif( nargin == 2 && ndims(varargin{1}) == 3 ) pfs.channels.X = single(varargin{1}(:,:,1)); pfs.channels.Y = single(varargin{1}(:,:,2)); pfs.channels.Z = single(varargin{1}(:,:,3)); else error( 'pfs_write_xyz: improper usage' ); end pfsput( pfs ); pfsclose( pfs ); pfspclose( fid ); end pfstools-2.2.0/src/matlab/pfspopen.m0000664000701400070140000000063014105165615016120 0ustar rkm38rkm38% Execute the command line and create a fifo pipe for reading/writing % output/input. Used internally by pfstools. % % fid = pfspopen( command_line, mode ) % % command_line - shell command line that should be executed % mode - either 'r' for reading output, or 'w' for writing input % fid - file ID, which should be passed to pfsopen % % Always use pfspclose() to close the pipe created with pfspopen pfstools-2.2.0/src/matlab/pfs_read_rgbe.cpp0000664000701400070140000000503714105165615017404 0ustar rkm38rkm38/** * @brief Read RGBE files * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Rafal Mantiuk and Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Rafal Mantiuk, * * $Id: pfs_read_rgbe.cpp,v 1.2 2007/03/01 14:00:44 rdmantiuk Exp $ */ #include "compatibility.h" #include #include #include #include #include "mex.h" #include "mex_utils.h" #include #define SCRIPT_NAME "pfs_read_rgbe" #define error mexErrMsgTxt #include void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) { /* Check for proper number of arguments. */ if( nrhs != 1 || !mxIsChar( prhs[0] ) ) error(SCRIPT_NAME ": Improper usage!"); try { const char *file_name = get_mex_string( prhs[0] ); FILE *fh = fopen( file_name, "rb" ); if( fh == NULL ) { error(SCRIPT_NAME ": Cannot open file for reading."); } RGBEReader reader( fh ); const int rows = reader.getHeight(), cols = reader.getWidth(); const int dims[] = { rows, cols, 3 }; plhs[0] = mxCreateNumericArray( 3, dims, mxDOUBLE_CLASS, mxREAL ); pfs::Array2DImpl ch1( cols, rows ), ch2( cols, rows ), ch3( cols, rows ); reader.readImage( &ch1, &ch2, &ch3 ); //pfs::transformColorSpace( pfs::CS_XYZ, &ch1, &ch2, &ch3, pfs::CS_RGB, &ch1, &ch2, &ch3 ); copy_pfsarray_to_mex( &ch1, &ch2, &ch3, plhs[0] ); } catch( pfs::Exception ex ) { char error_message[100]; sprintf( error_message, "%s: %s", SCRIPT_NAME, ex.getMessage() ); error( error_message ); } } pfstools-2.2.0/src/matlab/pfsput.m0000664000701400070140000000100414105165615015603 0ustar rkm38rkm38% Write frame to the pfs stream. See also help for pfsopen function. % % usage: pfsput( pfs_struct ); % pfs_struct - the structure returned by pfsopen. The stream must be open for writing % % Typical usage: % % ## Create you image % Y = ones( 512, 512 ); % Y(256,:) = 10; % % ## Create pfs stream for writing % pout = pfsopen( \"output.pfs\", size( Y ) ); % % ## Add channels % pout.channels.Y = Y; % % ## Write frame. You can 'put' multiple frames. % pfsput( pout ); % % ##Close stream % pfsclose( pout ) pfstools-2.2.0/src/matlab/pfs_fix_path.m0000664000701400070140000000172614105165615016747 0ustar rkm38rkm38function [fixed_path, cygwin_home] = pfs_fix_path( path ) % Used to fix paths when running cygwin on Windows. This is an internal % command. Do not use. if ~ispc() fixed_path = path; else pstatus = dos('set CYGWIN_HOME > nul 2>&1'); if(pstatus == 1) check_dirs = { 'c:\\cygwin', 'c:\\cygwin64', 'c:\\cygwin32' }; cygwin_home = []; for kk=1:length(check_dirs) if exist( check_dirs{kk}, 'dir' ) cygwin_home = check_dirs{kk}; break; end end if isempty( cygwin_home ) error( 'Cannot find Cygwin home directory. Set CYGWIN_HOME environment variable.' ); end else [pstatus, cygwin_home] = dos('echo %CYGWIN_HOME%'); cygwin_home = strtrim(cygwin_home); % to remove the final LF end [pstatus, fixed_path] = dos( [ fullfile( cygwin_home, 'bin', 'cygpath' ) ' ' path] ); fixed_path = strtrim( fixed_path ); end endpfstools-2.2.0/src/matlab/pfs_read_image.m0000664000701400070140000000543514105165615017223 0ustar rkm38rkm38function img = pfs_read_image( file_name, pfsin_command, extra_arguments ) %PFS_READ_IMAGE Read image file and return RGB, luminance or multichannel matrix. % % IMG = PFS_READ_IMAGE( file_name ) % IMG = PFS_READ_IMAGE( file_name, pfsin_command ) % IMG = PFS_READ_IMAGE( file_name, pfsin_command, extra_arguments ) % % Read an HDR or SDR image using pfstools. % % pfsin_command could be one of pfsin* command tools, such as 'pfsindcraw' or % 'pfsinimgmagick'. By default 'pfsin' is used, which can automatically % detect the image format from an extension and call the right pfsin % commnand. This option can be useful if pfsin does not recognize the image % format or you want to pass extra options to the command with % "extra_arguments". % % extra_arguments is a string that specified extra options to be passed to % pfsin command. For example, when used in combination with 'pfsindcraw', % you can pass '-n' to return the image in the native camera color space % instead of rec.709 RGB. % % If input is a gray-scale or luminance image, IMG is a 2D matrix. If input is % a color image, IMG(:,:,k) represents red, blue and green color channels for k=1,2 and 3. % If input is a multi-channel image (channel names C1, C2, ..., Cn), IMG is a % 3D matrix with 3rd dimension corresponding to the channels. % % PFS_READ_IMAGE accepts all formats recognized by the shell "pfsin" % command. % % Example: % img = PFS_READ_IMAGE( 'hdr_images/memorial.exr' ); % % See also: PFS_READ_RGB, PFS_READ_LUMINANCE, PFS_READ_XYZ, % PFS_WRITE_IMAGE, PFSVIEW. % % Copyright 2009 Rafal Mantiuk if ~exist( 'pfs_command', 'var' ) pfsin_command = 'pfsin'; end if ~exist( 'extra_arguments', 'var' ) extra_arguments = ''; end %Check if file exists fid = fopen( file_name, 'rb' ); if( fid == -1 ) error( 'pfs_read_image: File "%s" does not exist', file_name ); end fclose( fid ); fid = pfspopen( sprintf( '%s%s %s ''%s''%s', pfs_shell(), pfsin_command, extra_arguments, pfs_fix_path(file_name), pfs_shell( 1 ) ), 'r' ); pin = pfsopen( fid ); pin = pfsget( pin ); if( isfield( pin.channels, 'X' ) && isfield( pin.channels, 'Z' ) ) img = pfs_transform_colorspace( 'XYZ', pin.channels.X, pin.channels.Y, pin.channels.Z, 'RGB' ); elseif( isfield( pin.channels, 'Y' ) ) img = pin.channels.Y; elseif( isfield( pin.channels, 'C1' ) ) ch=1; % count the number of channels while( isfield( pin.channels, sprintf( 'C%d', ch ) ) ) ch = ch+1; end ch_max = ch-1; img = zeros(pin.rows, pin.columns, ch_max); for ch=1:ch_max img(:,:,ch) = pin.channels.(sprintf( 'C%d', ch )); end else error( 'Color channels missing in the pfs frame' ); end pfsclose( pin ); % TODO: Check why crashes on windows if ~ispc() pfspclose( fid ); end end pfstools-2.2.0/src/matlab/pfsview.m0000664000701400070140000000723114105165615015755 0ustar rkm38rkm38function pfsview( varargin ) %PFSVIEW Shows set of matrices as images using pfsview % % PFSVIEW( ['title',] channel [,'channel name'], ... ) % % title - (optional) a title to be displayed in the title bar of pfsview % channel - a real matrix, or a cell array of real matrixes. If the matrix % has the size(..,3)==3, it is recognized as an RGB color % image. % channel name - (optional) name for the channel given as next argument % after channel matrix. % % See also: PFS_READ_IMAGE, PFS_WRITE_IMAGE, PFS_TRANSFORM_COLORSPACE. % % Copyright 2009 Rafal Mantiuk width = -1; height = -1; title = 'matlab'; n = 1; for i=1:length( varargin ) if isa( varargin{i}, 'gpuArray' ) img = gather( varargin{i} ); else img = varargin{i}; end if iscell(img) C = img; width = size( C{1}, 2 ); height = size( C{1}, 1 ); if( size( C, 1 ) > 1 ) for j=1:size(C,1) for k=1:size(C,2) if( any( size(C{j,k}) ~= [height width] ) ) continue; % empty or invalid cell end ch_name = sprintf( 'c_%d_%dx%d', n, j, k ); channels.(ch_name) = C{j, k}; end end else for j=1:length(C) ch_name = sprintf( 'cell_%d_%d', n, j ); channels.(ch_name) = C{j}; end end n = n+1; elseif( isnumeric( img ) ) m_size = size( img ); width = m_size(2); height = m_size(1); if( length(m_size) == 2 ) matrix_name = inputname( i ); if( isempty( matrix_name ) ) ch_name = sprintf( 'matrix_%d', n ); else ch_name = matrix_name; end n = n+1; channels.( ch_name ) = single( img ); elseif( length(m_size) == 3 && m_size(3)==3 ) % Color channels [channels.X channels.Y channels.Z] = pfs_transform_colorspace( 'RGB', single(img), 'XYZ' ); elseif( length(m_size) == 3 && m_size(3)<10 ) matrix_name = inputname( i ); if( isempty( matrix_name ) ) ch_name = sprintf( 'matrix_%d', n ); n = n+1; else ch_name = matrix_name; end for j=1:m_size(3) channels.( sprintf( '%s_%d', ch_name, j ) ) = single( img(:,:,j) ); end else error( [ 'Cannot display matrix of the size [' num2str( m_size ) ']' ] ); end elseif( islogical( img ) ) ch_name = sprintf( 'bool_%d', n ); n = n+1; channels.(ch_name) = double(img); m_size = size( img ); width = m_size(2); height = m_size(1); elseif( ischar( img ) ) if( i == 1 ) title = img; else if( ~exist( 'channels', 'var' ) || ~exist( 'ch_name', 'var' ) ) error( 'channel_name argument must follow channel argument' ); else channels.(img) = channels.( ch_name ); channels = rmfield( channels, ch_name ); clear ch_name; end end end end % tmp file is used rather than pipes to run pfsview in background without % blocking matlab tmp_file = tempname(); pfsout = pfsopen( tmp_file, height, width ); pfsout.channels = channels; pfsout.tags.FILE_NAME = title; pfsput( pfsout ); pfsclose( pfsout ); tmp_file_path = pfs_fix_path( tmp_file ); % Fix paths on Windows system( sprintf( '%s(pfsview <''%s'' && rm -f ''%s'') &%s', pfs_shell(), tmp_file_path, tmp_file_path, pfs_shell( 1 ) ) ); end pfstools-2.2.0/src/matlab/pfs_transform_colorspace.cpp0000664000701400070140000001106414105165615021714 0ustar rkm38rkm38/** * @brief Tranform between color spaces using pfs library in MATLAB * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Rafal Mantiuk and Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Rafal Mantiuk, * * $Id: pfs_transform_colorspace.cpp,v 1.10 2014/02/16 19:02:30 rafm Exp $ */ #include "compatibility.h" #include #include #include #include #include #include #include "mex.h" #include "mex_utils.h" #define SCRIPT_NAME "pfs_transform_colorspace" #define error mexErrMsgTxt #include #include static pfs::ColorSpace findColorSpace( const char *name ); void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) { /* Check for proper number of arguments. */ if( (nrhs != 5 || !mxIsChar( prhs[0] ) || !( mxIsDouble( prhs[1] ) || mxIsSingle( prhs[1] ) ) || !( mxIsDouble( prhs[2] ) || mxIsSingle( prhs[2] ) ) || !( mxIsDouble( prhs[3] ) || mxIsSingle( prhs[3] ) ) || !mxIsChar( prhs[4] )) && (nrhs != 3 || !mxIsChar( prhs[0] ) || !( mxIsDouble( prhs[1] ) || mxIsSingle( prhs[1] ) ) || mxGetNumberOfDimensions( prhs[1] ) != 3 || !mxIsChar( prhs[2] )) ) error(SCRIPT_NAME ": Wrong number or type of input parameters!"); if( nlhs != 3 && nlhs != 1 ) error(SCRIPT_NAME ": Wrong number of output parameters!"); try { pfs::ColorSpace inCS = findColorSpace( get_mex_string( prhs[0] ) ); pfs::ColorSpace outCS = findColorSpace( get_mex_string( prhs[nrhs-1] ) ); const mwSize *in_dim = mxGetDimensions( prhs[1] ); const int rows = (int)in_dim[0], cols = (int)in_dim[1]; // printf( "%d x %d\n", rows, cols ); pfs::Array2DImpl c1Buf( cols, rows ), c2Buf( cols, rows ), c3Buf( cols, rows ); if( nrhs == 5 ) { copy_mex_to_pfsarray( prhs[1], &c1Buf ); copy_mex_to_pfsarray( prhs[2], &c2Buf ); copy_mex_to_pfsarray( prhs[3], &c3Buf ); } else { copy_mex_to_pfsarray( prhs[1], &c1Buf, &c2Buf, &c3Buf ); } pfs::transformColorSpace( inCS, &c1Buf, &c2Buf, &c3Buf, outCS, &c1Buf, &c2Buf, &c3Buf ); if( nlhs == 3 ) { plhs[0] = mxCreateNumericMatrix( rows, cols, mxDEFAULT_ARRAY_CLASS, mxREAL ); plhs[1] = mxCreateNumericMatrix( rows, cols, mxDEFAULT_ARRAY_CLASS, mxREAL ); plhs[2] = mxCreateNumericMatrix( rows, cols, mxDEFAULT_ARRAY_CLASS, mxREAL ); copy_pfsarray_to_mex( &c1Buf, plhs[0] ); copy_pfsarray_to_mex( &c2Buf, plhs[1] ); copy_pfsarray_to_mex( &c3Buf, plhs[2] ); } else if( nlhs == 1 ) { mwSize dims[] = { (mwSize)rows, (mwSize)cols, 3 }; plhs[0] = mxCreateNumericArray( 3, dims, mxDEFAULT_ARRAY_CLASS, mxREAL ); copy_pfsarray_to_mex( &c1Buf, &c2Buf, &c3Buf, plhs[0] ); } } catch( pfs::Exception ex ) { std::stringstream error_message; error_message << SCRIPT_NAME << ": " << ex.getMessage(); error( error_message.str().c_str() ); } } static pfs::ColorSpace findColorSpace( const char *name ) { if( !strcasecmp( name, "XYZ" ) ) return pfs::CS_XYZ; else if( !strcasecmp( name, "RGB" ) ) return pfs::CS_RGB; else if( !strcasecmp( name, "SRGB" ) ) return pfs::CS_SRGB; else if( !strcasecmp( name, "YUV" ) ) return pfs::CS_YUV; else if( !strcasecmp( name, "YXY" ) ) return pfs::CS_Yxy; else if( !strcasecmp( name, "RGB2020" ) ) return pfs::CS_RGB2020; else if( !strcasecmp( name, "PQYCbCr2020" ) ) return pfs::CS_PQYCbCr2020; else if( !strcasecmp( name, "HLGYCbCr2020") ) return pfs::CS_HLGYCbCr2020; else if( !strcasecmp( name, "YCbCr709" ) ) return pfs::CS_YCbCr709; throw pfs::Exception( "Not recognized color space" ); } pfstools-2.2.0/src/matlab/pfsput.cpp0000664000701400070140000001471714105165615016150 0ustar rkm38rkm38/** * @brief Write frame to pfs stream in MATLAB * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Rafal Mantiuk and Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Rafal Mantiuk, * * $Id: pfsput.cpp,v 1.6 2008/05/06 17:11:35 rafm Exp $ */ #include "compatibility.h" #include #include #include #include #include "mex.h" #include "mex_utils.h" #define SCRIPT_NAME "pfsput" #define error mexErrMsgTxt #include void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) { /* Check for proper number of arguments. */ if( nrhs != 1 || !mxIsStruct( prhs[0] ) ) error(SCRIPT_NAME ": Improper usage!"); const mxArray *pfs_stream = prhs[0]; mxArray *f_fid = mxGetField( pfs_stream, 0, "FID" ); if( f_fid == NULL || !is_mex_scalar( f_fid ) ) { error( SCRIPT_NAME ": FH field missing in the structure or it has wrong type"); } int fid = (int)get_mex_double( f_fid ); // Check mode { mxArray *f_mode = mxGetField( pfs_stream, 0, "MODE" ); if( f_mode == NULL ) { error( SCRIPT_NAME ": MODE field missing in the structure or it has wrong type"); } if( strcmp( "W", get_mex_string( f_mode ) ) ) { error( SCRIPT_NAME ": Can not write to the stream that is open for reading." ); } } // Get width & height int width, height; { mxArray *f_cols, *f_rows; f_cols = mxGetField( pfs_stream, 0, "columns" ); f_rows = mxGetField( pfs_stream, 0, "rows" ); if( !is_mex_scalar( f_cols ) || !is_mex_scalar( f_rows ) ) { error( SCRIPT_NAME ": 'rows' and 'columns' fields missing in the structure or it have wrong type"); } width = (int)get_mex_double( f_cols ); height = (int)get_mex_double( f_rows ); } // Get channels int ch_count; mxArray *f_channels; { f_channels = mxGetField( pfs_stream, 0, "channels" ); if( f_channels == NULL || !mxIsStruct( f_channels ) ) error( SCRIPT_NAME ": 'channels' field missing in the structure or it has wrong type"); ch_count = mxGetNumberOfFields( f_channels ); } try { pfs::DOMIO ctx; pfs::Frame *frame = ctx.createFrame( width, height ); // For each channel in the 'channels' map for( int ch = 0; ch < ch_count; ch++ ) { const char *channelName = mxGetFieldNameByNumber( f_channels, ch ); mxArray *f_data = mxGetFieldByNumber( f_channels, 0, ch ); if( !(mxIsDouble( f_data ) || mxIsSingle( f_data )) || mxIsComplex( f_data ) ) { throw pfs::Exception( "all channels must be given as real matrices" ); } if( mxGetM( f_data ) != height || mxGetN( f_data ) != width ) { throw pfs::Exception( "size of the channel must be the same as given in pfsopen" ); } pfs::Channel *pfsChannel = frame->createChannel( channelName ); // Copy matrix to pfs::Channel copy_mex_to_pfschannel( f_data, pfsChannel ); } // Copy frame tags { mxArray *tags, *f_rows; tags = mxGetField( pfs_stream, 0, "tags" ); if( tags != NULL ) { if( !mxIsStruct( f_channels ) ) error( SCRIPT_NAME ": 'tags' field has wrong type"); int tag_count = mxGetNumberOfFields( tags ); for( int t = 0; t < tag_count; t++ ) { const char *tag_name = mxGetFieldNameByNumber( tags, t ); mxArray *tag_value = mxGetFieldByNumber( tags, 0, t ); // printf( "tag: %s = %s\n", tag_name, get_mex_string( tag_value ) ); frame->getTags()->setString( tag_name, get_mex_string( tag_value ) ); } } } /* Not implemented - hopefuly nobody uses this // Copy channel tags { Octave_map::const_iterator itChTags = pfsStream.seek( "channelTags" ); if( itChTags != pfsStream.end() ) { if( !pfsStream.contents( itChTags )(0).isstruct() ) { throw pfs::Exception( "'channelTags' field must be a structure" ); } Octave_map tagChannels = pfsStream.contents( itChTags )(0).map_value(); for( Octave_map::iterator itCh = tagChannels.begin(); itCh != tagChannels.end(); itCh++ ) { std::string channelName = tagChannels.key(itCh); if( !tagChannels.contents( itCh )(0).isstruct() ) { throw pfs::Exception( "each channelTags file must be a structure" ); } pfs::Channel *pfsChannel = frame->getChannel( channelName.c_str() ); if( pfsChannel == NULL ) { throw pfs::Exception( "can not set channel tag if channel is missing" ); } Octave_map tags = tagChannels.contents( itCh )(0).map_value(); for( Octave_map::iterator itTag = tags.begin(); itTag != tags.end(); itTag++ ) { std::string tagName = tags.key(itTag); if( !tags.contents( itTag )(0).is_string() ) throw pfs::Exception( "all channel tags must be given as strings" ); std::string tagValue = tags.contents( itTag )(0).string_value(); pfsChannel->getTags()->setString( tagName.c_str(), tagValue.c_str() ); } } } } */ // FILE *fh = fdopen( fid, "a" ); FILE *fh = fdopen( dup( fid ), "w" ); if( fh == NULL ) error( SCRIPT_NAME ": Cannot open file for writing" ); ctx.writeFrame( frame, fh ); ctx.freeFrame( frame ); fclose( fh ); //fsync( fid ); } catch( pfs::Exception ex ) { char error_message[100]; sprintf( error_message, "%s: %s", SCRIPT_NAME, ex.getMessage() ); error( error_message ); } } pfstools-2.2.0/src/fileformat/0002775000701400070140000000000014105165615015003 5ustar rkm38rkm38pfstools-2.2.0/src/fileformat/pfsingdal.cpp0000664000701400070140000001730014105165615017455 0ustar rkm38rkm38/** * @brief Read files using GDAL. * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2008 Martin Lambers * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Martin Lambers, * * $Id: pfsingdal.cpp,v 1.1 2008/05/15 00:03:56 rafm Exp $ */ #include #include #include #include #include #include #include #include #include #define PROG_NAME "pfsingdal" #ifndef SIZE_MAX #define SIZE_MAX ((size_t)-1) #endif class QuietException { }; std::string stringify(double x) { std::ostringstream o; o.precision( DBL_DIG ); o << x; return o.str(); } void printHelp() { std::cerr << PROG_NAME " [--verbose] [--help]" << std::endl << "See man page for more information." << std::endl; } void readFrames( int argc, char* argv[] ) { pfs::DOMIO pfsio; bool verbose = false; // Parse command line parameters static struct option cmdLineOptions[] = { { "help", no_argument, NULL, 'h' }, { "verbose", no_argument, NULL, 'v' }, { NULL, 0, NULL, 0 } }; static const char optstring[] = "hv"; pfs::FrameFileIterator it( argc, argv, "rb", NULL, NULL, optstring, cmdLineOptions ); int optionIndex = 0; while( 1 ) { int c = getopt_long (argc, argv, optstring, cmdLineOptions, &optionIndex); if( c == -1 ) break; switch( c ) { case 'h': printHelp(); throw QuietException(); case 'v': verbose = true; break; case '?': throw QuietException(); case ':': throw QuietException(); } } GDALAllRegister(); GDALDataset *poDataset; GDALRasterBand *poBand; double adfGeoTransform[6]; size_t nBlockXSize, nBlockYSize, nBands; int bGotMin, bGotMax; double adfMinMax[2]; float *pafScanline; while( true ) { pfs::FrameFile ff = it.getNextFrameFile(); if( ff.fh == NULL ) break; // No more frames it.closeFrameFile( ff ); VERBOSE_STR << "reading file '" << ff.fileName << "'" << std::endl; if( !( poDataset = (GDALDataset *) GDALOpen( ff.fileName, GA_ReadOnly ) ) ) { std::cerr << "input does not seem to be in a format supported by GDAL" << std::endl; throw QuietException(); } VERBOSE_STR << "GDAL driver: " << poDataset->GetDriver()->GetDescription() << " / " << poDataset->GetDriver()->GetMetadataItem( GDAL_DMD_LONGNAME ) << std::endl; nBlockXSize = poDataset->GetRasterXSize(); nBlockYSize = poDataset->GetRasterYSize(); nBands = poDataset->GetRasterCount(); VERBOSE_STR << "Data size " << nBlockXSize << "x" << nBlockYSize << "x" << nBands << std::endl; if( poDataset->GetProjectionRef() ) { VERBOSE_STR << "Projection " << poDataset->GetProjectionRef() << std::endl; } if( poDataset->GetGeoTransform( adfGeoTransform ) == CE_None ) { VERBOSE_STR << "Origin = (" << adfGeoTransform[0] << ", " << adfGeoTransform[3] << ")" << std::endl; VERBOSE_STR << "Pixel Size = (" << adfGeoTransform[1] << ", " << adfGeoTransform[5] << ")" << std::endl; } if( nBlockXSize==0 || nBlockYSize==0 || ( SIZE_MAX / nBlockYSize < nBlockXSize ) || ( SIZE_MAX / (nBlockXSize * nBlockYSize ) < 4 ) ) { std::cerr << "input data has invalid size" << std::endl; throw QuietException(); } if( !(pafScanline = (float *) CPLMalloc( sizeof(float) * nBlockXSize ) ) ) { std::cerr << "not enough memory" << std::endl; throw QuietException(); } pfs::Frame *frame = pfsio.createFrame( nBlockXSize, nBlockYSize ); pfs::Channel *C[nBands]; char channel_name[32]; frame->getTags()->setString( "X-GDAL_DRIVER_SHORTNAME", poDataset->GetDriver()->GetDescription() ); frame->getTags()->setString( "X-GDAL_DRIVER_LONGNAME", poDataset->GetDriver()->GetMetadataItem( GDAL_DMD_LONGNAME ) ); frame->getTags()->setString( "X-PROJECTION", poDataset->GetProjectionRef() ); if( poDataset->GetGeoTransform( adfGeoTransform ) == CE_None ) { frame->getTags()->setString( "X-ORIGIN_X", stringify(adfGeoTransform[0]).c_str() ); frame->getTags()->setString( "X-ORIGIN_Y", stringify(adfGeoTransform[3]).c_str() ); frame->getTags()->setString( "X-PIXEL_WIDTH", stringify(adfGeoTransform[1]).c_str() ); frame->getTags()->setString( "X-PIXEL_HEIGHT", stringify(adfGeoTransform[5]).c_str() ); } for ( size_t band = 1; band <= nBands; band++) { size_t nBandXSize, nBandYSize; VERBOSE_STR << "Band " << band << ": " << std::endl; snprintf( channel_name, 32, "X-GDAL%zu", band ); C[band - 1] = frame->createChannel( channel_name ); poBand = poDataset->GetRasterBand( band ); nBandXSize = poBand->GetXSize(); nBandYSize = poBand->GetYSize(); VERBOSE_STR << " " << nBandXSize << "x" << nBandYSize << std::endl; if( nBandXSize != (int)nBlockXSize || nBandYSize != (int)nBlockYSize ) { std::cerr << "data in band " << band << " has different size" << std::endl; throw QuietException(); } VERBOSE_STR << " Type " << GDALGetDataTypeName( poBand->GetRasterDataType() ) << ", " << "Color Interpretation " << GDALGetColorInterpretationName( poBand->GetColorInterpretation() ) << std::endl; adfMinMax[0] = poBand->GetMinimum( &bGotMin ); adfMinMax[1] = poBand->GetMaximum( &bGotMax ); if( ! (bGotMin && bGotMax) ) { GDALComputeRasterMinMax((GDALRasterBandH)poBand, TRUE, adfMinMax); } VERBOSE_STR << " Min " << adfMinMax[0] << ", Max " << adfMinMax[1] << std::endl; C[band - 1]->getTags()->setString( "X-TYPE", GDALGetDataTypeName( poBand->GetRasterDataType() ) ); C[band - 1]->getTags()->setString( "X-COLOR_INTERPRETATION", GDALGetColorInterpretationName( poBand->GetColorInterpretation() ) ); C[band - 1]->getTags()->setString( "X-MIN", stringify(adfMinMax[0]).c_str() ); C[band - 1]->getTags()->setString( "X-MAX", stringify(adfMinMax[1]).c_str() ); for( size_t y = 0; y < nBlockYSize; y++ ) { if( poBand->RasterIO( GF_Read, 0, y, nBlockXSize, 1, pafScanline, nBlockXSize, 1, GDT_Float32, 0, 0) != CE_None ) { std::cerr << "input error" << std::endl; throw QuietException(); } memcpy( C[band - 1]->getRawData() + y * nBlockXSize, pafScanline, nBlockXSize * sizeof(float) ); } } CPLFree( pafScanline ); GDALClose( poDataset ); const char *fileNameTag = strcmp( "-", ff.fileName )==0 ? "stdin" : ff.fileName; frame->getTags()->setString( "FILE_NAME", fileNameTag ); pfsio.writeFrame( frame, stdout ); pfsio.freeFrame( frame ); } } int main( int argc, char* argv[] ) { try { readFrames( argc, argv ); } catch( pfs::Exception ex ) { std::cerr << PROG_NAME << " error: " << ex.getMessage() << std::endl; return EXIT_FAILURE; } catch( QuietException ex ) { return EXIT_FAILURE; } return EXIT_SUCCESS; } pfstools-2.2.0/src/fileformat/hdrtiffio.h0000664000701400070140000000516314105165615017135 0ustar rkm38rkm38/** * @brief IO operations on High Dynamic Range TIFF file format * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Rafal Mantiuk and Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Grzegorz Krawczyk, * * $Id: hdrtiffio.h,v 1.3 2007/03/13 09:25:49 gkrawczyk Exp $ */ #ifndef _HDRTIFFIO_H_ #define _HDRTIFFIO_H_ #include #include class HDRTiffReader { TIFF* tif; uint32 width, height; uint16 comp; /// compression type uint16 phot; /// type of photometric data enum {FLOAT, WORD, BYTE, GRAYSCALE16} TypeOfData; uint16 bps; /// bits per sample uint16 nSamples; /// number of channels in tiff file (only 1-3 are used) double stonits; /// scale factor to get nit values bool exponential_mode; /// if true, grayscale data come from LarsIII camera bool relative_values; /// if true, values are linearized (hdr data) bool xyz_colorspace; /// if true, values are in XYZ colorspace char format_string[255]; /// for verbose output public: HDRTiffReader( const char* filename ); ~HDRTiffReader(); int getWidth() const { return width; } int getHeight() const { return height; } void readImage( pfs::Array2D *X, pfs::Array2D *Y, pfs::Array2D *Z ); void setExponentialMode() { exponential_mode = true; relative_values=true; } const char* getFormatString() { return format_string; } bool isRelative() { return relative_values; } bool isColorspaceXYZ() { return xyz_colorspace; } }; class HDRTiffWriter { FILE *file; public: HDRTiffWriter( FILE *fh ) : file(fh) { } void writeImage( pfs::Array2D *X, pfs::Array2D *Y, pfs::Array2D *Z ); }; #endif pfstools-2.2.0/src/fileformat/hdrtiffio.cpp0000664000701400070140000003411514105165615017467 0ustar rkm38rkm38/** * @brief IO operations on High Dynamic Range TIFF file format * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Rafal Mantiuk and Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Grzegorz Krawczyk, * * $Id: hdrtiffio.cpp,v 1.7 2014/01/03 12:52:20 rafm Exp $ */ #include #include #include #include #include #include "hdrtiffio.h" using namespace std; int TIFFWriteIFD(FILE* file, unsigned short tag, unsigned short type, unsigned long count, unsigned long value); //--- // HDR TIFF IO classes implementation HDRTiffReader::HDRTiffReader( const char* filename ) { // default values for constants exponential_mode = false; relative_values = false; xyz_colorspace = false; // read header containing width and height from file tif = TIFFOpen(filename, "r"); if( !tif ) throw pfs::Exception("TIFF: could not open file for reading."); //--- image size TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &width); TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &height); if( width*height<=0 ) { TIFFClose(tif); throw pfs::Exception("TIFF: illegal image size"); } DEBUG_STR << "TIFF file \"" << filename << "\" (" << width << "x" << height << ")" << endl; //--- image parameters if(!TIFFGetField(tif, TIFFTAG_COMPRESSION, &comp)) // compression type comp = COMPRESSION_NONE; // type of photometric data if(!TIFFGetFieldDefaulted(tif, TIFFTAG_PHOTOMETRIC, &phot)) throw pfs::Exception("TIFF: unspecified photometric type"); uint16 * extra_sample_types=0; uint16 extra_samples_per_pixel=0; switch(phot) { case PHOTOMETRIC_LOGLUV: DEBUG_STR << "Photometric data: LogLuv" << endl; if (comp != COMPRESSION_SGILOG && comp != COMPRESSION_SGILOG24) { TIFFClose(tif); throw pfs::Exception("TIFF: only support SGILOG compressed LogLuv data"); } TIFFGetField(tif, TIFFTAG_SAMPLESPERPIXEL, &nSamples); // set decoder to output in float XYZ TIFFSetField(tif, TIFFTAG_SGILOGDATAFMT, SGILOGDATAFMT_FLOAT); xyz_colorspace = true; TypeOfData = FLOAT; strcpy(format_string,"linear LogLuv XYZ"); relative_values=true; break; case PHOTOMETRIC_RGB: DEBUG_STR << "Photometric data: RGB" << endl; // read extra samples (# of alpha channels) if (TIFFGetField( tif, TIFFTAG_EXTRASAMPLES, &extra_samples_per_pixel, &extra_sample_types )!=1) { extra_samples_per_pixel=0; } TIFFGetField(tif, TIFFTAG_SAMPLESPERPIXEL, &nSamples); bps = nSamples - extra_samples_per_pixel; if (bps!=3) { TIFFClose(tif); throw pfs::Exception("TIFF: unsupported samples per pixel for RGB"); } if (!TIFFGetField(tif, TIFFTAG_BITSPERSAMPLE, &bps) || (bps!=8 && bps!=16 && bps!=32)) { TIFFClose(tif); throw pfs::Exception("TIFF: unsupported bits per sample for RGB"); } if( bps==8 ) { TypeOfData = BYTE; DEBUG_STR << "8bit per channel" << endl; strcpy(format_string,"linear 8bit RGB"); relative_values=false; //!! TODO: verify if 8bit is always gamma corrected } else if( bps==16 ) { TypeOfData = WORD; DEBUG_STR << "16bit per channel" << endl; strcpy(format_string,"linear 16bit RGB"); relative_values=true; //!! TODO: verify this case!! } else { TypeOfData = FLOAT; DEBUG_STR << "32bit float per channel" << endl; strcpy(format_string,"linear 32bit float RGB"); relative_values=true; } break; case PHOTOMETRIC_MINISBLACK: // HDR video camera format (grayscale) DEBUG_STR << "Photometric data: MINISBLACK (hdrv camera)" << endl; if (!TIFFGetField(tif, TIFFTAG_SAMPLESPERPIXEL, &bps) || bps!=1) { TIFFClose(tif); throw pfs::Exception("TIFF: Unsupported samples per pixel for " "grayscale image"); } if (!TIFFGetField(tif, TIFFTAG_BITSPERSAMPLE, &bps) || bps!=16) { DEBUG_STR << "Detected bits per sample: " << bps << endl; TIFFClose(tif); throw pfs::Exception("TIFF: unsupported bits per sample for " "grayscale image."); } TypeOfData = GRAYSCALE16; strcpy(format_string,"linear 16bit"); relative_values=true; break; default: DEBUG_STR << "Unsupported photometric type: " << phot << endl; TIFFClose(tif); strcpy(format_string,"unknown"); relative_values=false; throw pfs::Exception("TIFF: unsupported photometric type"); } if (!TIFFGetField(tif, TIFFTAG_STONITS, &stonits)) stonits = 1.; } void HDRTiffReader::readImage( pfs::Array2D *X, pfs::Array2D *Y, pfs::Array2D *Z ) { //--- scanline buffer with pointers to different data types union { float* fp; uint16* wp; uint8* bp; void* vp; } buf; //--- image length uint32 imagelength; TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &imagelength); DEBUG_STR << "Image length: " << imagelength << endl; //--- image scanline size uint32 scanlinesize = TIFFScanlineSize(tif); buf.vp = _TIFFmalloc(scanlinesize); // DEBUG_STR << "Scanline size: " << scanlinesize << endl; //--- read scan lines const int image_width = X->getCols(); for(uint32 row = 0; row < imagelength; row++) { switch(TypeOfData) { case FLOAT: TIFFReadScanline(tif, buf.fp, row); for( int i=0; i < image_width; i++ ) { (*X)(i,row) = buf.fp[i*nSamples]; (*Y)(i,row) = buf.fp[i*nSamples+1]; (*Z)(i,row) = buf.fp[i*nSamples+2]; } break; case WORD: TIFFReadScanline(tif, buf.wp, row); for( int i=0; i> 12; float lum = value*pow2[expo]; (*X)(i,imagelength-row-1) = lum; (*Y)(i,imagelength-row-1) = lum; (*Z)(i,imagelength-row-1) = lum; } } break; } } //--- free buffers and close files _TIFFfree(buf.vp); TIFFClose(tif); } HDRTiffReader::~HDRTiffReader() { } void HDRTiffWriter::writeImage( pfs::Array2D *X, pfs::Array2D *Y, pfs::Array2D *Z ) { // image size int width = X->getCols(); int height = X->getRows(); // Offset in tiff image unsigned long tiff_offset=0; //--- Tiff header unsigned char head[4]; head[0] = 'I'; // byte order head[1] = 'I'; head[2] = 0x2a; // tiff version head[3] = 0x00; fwrite(head, sizeof(unsigned char), 4, file); tiff_offset+=4; //--- Tiff image data int rows_per_strip = 3; int no_of_strips = ((height+rows_per_strip-1) / rows_per_strip); unsigned long* strip_offsets = new unsigned long[no_of_strips]; unsigned long* strip_byte_counts = new unsigned long[no_of_strips]; //--- first IFD pointer // (67 - ifd values block) unsigned long first_ifd_offs = width*height*3+no_of_strips*4*2+67; fwrite(&first_ifd_offs, sizeof(unsigned long), 1, file); tiff_offset+=4; //-------------------------------------------------------------------- //--- writing image data int strip_len = rows_per_strip*width*3; int bytes_count = 0; //bytes to write int strip_no = 0; // no of strip unsigned char* strip = new unsigned char[strip_len]; for( int y=0 ; y0) ? ( (r<255.0f)?r:255.0f) : 0; g = (g>0) ? ( (g<255.0f)?g:255.0f) : 0; b = (b>0) ? ( (b<255.0f)?b:255.0f) : 0; strip[bytes_count++] = (unsigned char) r; strip[bytes_count++] = (unsigned char) g; strip[bytes_count++] = (unsigned char) b; } if( y%rows_per_strip == rows_per_strip-1 ) { // write strip strip_offsets[strip_no] = tiff_offset; strip_byte_counts[strip_no] = bytes_count; fwrite(strip, sizeof(unsigned char), bytes_count, file); tiff_offset+=bytes_count; strip_no++; bytes_count=0; } } // write remaining strip if( bytes_count>0 ) { // write strip strip_offsets[strip_no] = tiff_offset; strip_byte_counts[strip_no] = bytes_count; fwrite(strip, sizeof(unsigned char), bytes_count, file); tiff_offset+=bytes_count; strip_no++; bytes_count=0; } delete[] strip; //-------------------------------------------------------------------- //--- Tiff data unsigned char software[] = "PFSTOOLS tiff io operations"; unsigned long software_offs = tiff_offset; int software_len = 37; fwrite(software, sizeof(unsigned char), software_len, file); tiff_offset+=37; unsigned short bits_per_sample[3] = {8,8,8}; unsigned long bits_per_sample_offs=tiff_offset; fwrite(bits_per_sample, sizeof(unsigned short), 3, file); tiff_offset+=3*2; unsigned long strip_byte_counts_offs = tiff_offset; fwrite(strip_byte_counts, sizeof(unsigned long), no_of_strips, file); tiff_offset+=no_of_strips*4; unsigned long strip_offsets_offs = tiff_offset; fwrite(strip_offsets, sizeof(unsigned long), no_of_strips, file); tiff_offset+=no_of_strips*4; unsigned long xresolution[] = { 72, 1 }; // cecha i mantysa unsigned long xresolution_offs = tiff_offset; fwrite(xresolution, sizeof(unsigned long), 2, file); tiff_offset+=2*4; unsigned long yresolution[] = { 72, 1 }; // cecha i mantysa unsigned long yresolution_offs = tiff_offset; fwrite(yresolution, sizeof(unsigned long), 2, file); tiff_offset+=2*4; //-- not used tiff data delete[] strip_offsets; delete[] strip_byte_counts; //--- check if offset is ok assert( first_ifd_offs==tiff_offset ); //--- image file directory unsigned short ifd_count=15; // ifd entry count fwrite(&ifd_count, sizeof(unsigned short), 1, file); tiff_offset+=2; // NewSubfileType: 0 - full resolution image TIFFWriteIFD(file, 0x00fe, 4, 1, 0); // ImageWidth TIFFWriteIFD(file, 0x0100, 4, 1, width); // ImageLength (Height) TIFFWriteIFD(file, 0x0101, 4, 1, height); // BitsPerSample TIFFWriteIFD(file, 0x0102, 3, 3, bits_per_sample_offs); // Compression: 1 - no compression TIFFWriteIFD(file, 0x0103, 3, 1, 1); // PhotometricInterpretation: 2 - RGB image TIFFWriteIFD(file, 0x0106, 3, 1, 2); // StripOffsets TIFFWriteIFD(file, 0x0111, 4, no_of_strips, strip_offsets_offs); // SamplesPerPixel: 3 TIFFWriteIFD(file, 0x0115, 3, 1, 3); // RowsPerStrip TIFFWriteIFD(file, 0x0116, 4, 1, rows_per_strip); // StripByteCounts TIFFWriteIFD(file, 0x0117, 4, no_of_strips, strip_byte_counts_offs); // XResolution TIFFWriteIFD(file, 0x011a, 5, 1, xresolution_offs); // YResolution TIFFWriteIFD(file, 0x011b, 5, 1, yresolution_offs); // PlanarConfiguration: 1 - single plannar TIFFWriteIFD(file, 0x011c, 3, 1, 1); // ResolutionUnit: 2 - cm TIFFWriteIFD(file, 0x0128, 3, 1, 2); // Software TIFFWriteIFD(file, 0x0131, 2, software_len, software_offs); tiff_offset+=ifd_count*12; //--- second and the last IFD pointer unsigned long last_ifd_offs = 0; fwrite(&last_ifd_offs, sizeof(unsigned long), 1, file); tiff_offset+=4; } int TIFFWriteIFD(FILE* file, unsigned short tag, unsigned short type, unsigned long count, unsigned long value) { // image file directory flag unsigned char ifd[12]; (unsigned short&)ifd[0] = tag; (unsigned short&)ifd[2] = type; (unsigned long&)ifd[4] = count; (unsigned long&)ifd[8] = 0; if( count>1 ) (unsigned long&)ifd[8] = value; else { switch( type ) { case 1: // byte (unsigned char&)ifd[8] = (unsigned char)value; break; case 3: // short (unsigned short&)ifd[8] = (unsigned short)value; break; case 5: // rational case 4: // long case 2: // ascii (unsigned long&)ifd[8] = value; break; }; } fwrite(ifd, sizeof(unsigned char), 12, file); return 0; } pfstools-2.2.0/src/fileformat/rgbeio.h0000664000701400070140000000361414105165615016425 0ustar rkm38rkm38/** * @brief IO operations on Radiance's RGBE file format * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Rafal Mantiuk and Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Grzegorz Krawczyk, * * $Id: rgbeio.h,v 1.4 2014/06/17 21:57:09 rafm Exp $ */ #ifndef _RGBEIO_H_ #define _RGBEIO_H_ #include #include class RGBEReader { FILE *fh; int width, height; float exposure; bool radiance_compatibility; public: RGBEReader( FILE *fh, bool radiance_compatibility = false ); ~RGBEReader(); int getWidth() const { return width; } int getHeight() const { return height; } void readImage( pfs::Array2D *X, pfs::Array2D *Y, pfs::Array2D *Z ); }; class RGBEWriter { FILE *fh; bool radiance_compatibility; public: RGBEWriter(FILE *fh, bool radiance_compatibility = false) : fh(fh), radiance_compatibility(radiance_compatibility) { } void writeImage( pfs::Array2D *X, pfs::Array2D *Y, pfs::Array2D *Z ); }; #endif pfstools-2.2.0/src/fileformat/pfsoutpfm.cpp0000664000701400070140000001402614105165615017533 0ustar rkm38rkm38/** * @brief Write files in PFM format * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Rafal Mantiuk and Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Rafal Mantiuk, * * The format description was based on: * http://netpbm.sourceforge.net/doc/pfm.html * * however the order of the lines in pfm file seems to be reversed - * from bottom to top * * $Id: pfsoutpfm.cpp,v 1.1 2005/06/15 13:36:54 rafm Exp $ */ #include #include #include #include #include #include #define PROG_NAME "pfsoutpfm" #define PFMEOL "\x0a" class QuietException { }; #define min(x,y) ( (x)<(y) ? (x) : (y) ) static bool isBigEndian() { int x = 1; char *y = (char*)&x; return y[0] == 0; } void printHelp() { fprintf( stderr, PROG_NAME " [--verbose] [--help]\n" "See man page for more information.\n" ); } void writePFMFileColor( FILE *fh, int width, int height, float *R, float *G, float *B ) { // Write header int scale = -1; if( isBigEndian() ) // Is this a big endian ? scale = 1; fprintf( fh, "PF" PFMEOL "%d %d" PFMEOL "%d" PFMEOL, width, height, scale ); const int lineSize = width*3; float *line = new float[lineSize]; // PFM images are stored in rows bottom to top for( int l = height-1; l >= 0; l-- ) { for( int x = 0; x < width; x++ ) { const int lineOffset = l*width; line[x*3+0] = R[lineOffset+x]; line[x*3+1] = G[lineOffset+x]; line[x*3+2] = B[lineOffset+x]; } int written = fwrite( line, sizeof( float ), lineSize, fh ); if( written != lineSize ) throw new pfs::Exception( "Unable to write data" ); } delete[] line; } void writePFMFileGrayscale( FILE *fh, int width, int height, float *Y ) { // Write header int scale = -1; if( isBigEndian() ) // Is this a big endian ? scale = 1; fprintf( fh, "Pf" PFMEOL "%d %d" PFMEOL "%d" PFMEOL, width, height, scale ); const int lineSize = width; float *line = new float[lineSize]; // PFM images are stored in rows bottom to top for( int l = height-1; l >= 0; l-- ) { for( int x = 0; x < width; x++ ) { const int lineOffset = l*width; line[x] = Y[lineOffset+x]; } int written = fwrite( line, sizeof( float ), lineSize, fh ); if( written != lineSize ) throw new pfs::Exception( "Unable to write data" ); } delete[] line; } void writeFrames( int argc, char* argv[] ) { bool verbose = false; // Parse command line parameters static struct option cmdLineOptions[] = { { "help", no_argument, NULL, 'h' }, { "verbose", no_argument, NULL, 'v' }, { "linear", no_argument, NULL, 'l' }, { NULL, 0, NULL, 0 } }; static const char optstring[] = "hlv"; pfs::FrameFileIterator it( argc, argv, "wb", NULL, stdout, optstring, cmdLineOptions ); int optionIndex = 0; while( 1 ) { int c = getopt_long (argc, argv, optstring, cmdLineOptions, &optionIndex); if( c == -1 ) break; switch( c ) { case 'h': printHelp(); throw QuietException(); case 'v': verbose = true; break; case 'l': std::cerr << PROG_NAME << " warning: linearize option ignored for an HDR output!" << std::endl; break; case '?': throw QuietException(); case ':': throw QuietException(); } } pfs::DOMIO pfsio; bool firstFrame = true; while( true ) { pfs::Frame *frame = pfsio.readFrame( stdin ); if( frame == NULL ) { break; // No more frames } const char* luminanceTag = frame->getTags()->getString("LUMINANCE"); if( luminanceTag!=NULL && 0==strcmp(luminanceTag,"DISPLAY") ) { static bool show_once = false; if( !show_once ) { std::cerr << PROG_NAME << " warning: " << "display profiled content written to an HDR output" << std::endl; show_once=true; } } pfs::FrameFile ff = it.getNextFrameFile(); if( ff.fh == NULL ) { pfsio.freeFrame( frame ); break; // No more frames } VERBOSE_STR << "writing file (HDR) '" << ff.fileName << "'" << std::endl; pfs::Channel *X, *Y, *Z; frame->getXYZChannels( X, Y, Z ); if( X != NULL ) { // Has color pfs::Channel *R= NULL, *G = NULL, *B = NULL; // for clarity of the code R = X; G = Y; B = Z; pfs::transformColorSpace( pfs::CS_XYZ, X, Y, Z, pfs::CS_RGB, R, G, B ); writePFMFileColor( ff.fh, frame->getWidth(), frame->getHeight(), R->getRawData(), G->getRawData(), B->getRawData() ); } else { Y = frame->getChannel( "Y" ); if( Y == NULL ) throw pfs::Exception( "Can not find color or grayscale channels in the pfs stream" ); writePFMFileGrayscale( ff.fh, frame->getWidth(), frame->getHeight(), Y->getRawData() ); } it.closeFrameFile( ff ); pfsio.freeFrame( frame ); } } int main( int argc, char* argv[] ) { try { writeFrames( argc, argv ); } catch( pfs::Exception ex ) { fprintf( stderr, PROG_NAME " error: %s\n", ex.getMessage() ); return EXIT_FAILURE; } catch( QuietException ex ) { return EXIT_FAILURE; } return EXIT_SUCCESS; } pfstools-2.2.0/src/fileformat/pfsoutppm.10000664000701400070140000000564314105165615017130 0ustar rkm38rkm38.TH "pfsoutppm" 1 .SH NAME pfsoutppm, pfsouttiff, pfsoutrgbe, pfsoutexr \- Write images or frames in one of the several formats .SH SYNOPSIS .B pfsoutppm ( [--srgb] [--frames ]) [...] .B pfsouttiff ( [--srgb] [--frames ]) [...] .B pfsoutrgbe ( [--frames ]) [...] .B pfsoutexr ( [--frames ]) [...] .SH DESCRIPTION This command can be used to write pfs frames piped to standard input in one of the several recognized formats. See the manual page of pfsinppm to get a list of available formats. .PP To write images to standard output use a single dash '-' instead of filename. .PP Each file name can contain a \%%d pattern, which is substituted with frame numbers. The pattern has the same syntax as C .I printf command. For example, you can use \%%04d to make the frame number four digit with proceedings zeros. You can select the frames using the following options (the options must be always given after the file name): .TP .B \--frames Range is given in mathlab / octave format: .B "startframe:step:endframe" Frame numbers start with .B "startframe" (default 0), are increased by .B "step" (default 1) and stop at .B "endframe" You can skip one of those values, for example .I "1:100" for frames 1,2,...,100 and .I 0:2: for frame 0,2,4,... up to the last file that exists. .TP .B --srgb, -s Apply the sRGB non-linearity (approximately 2.2 gamma) before writing a file. This can be used instead of using \fIpfsgamma -g 2.2\fR to store the result of some tone-mapping operators. The option will compress contrast to make sure that tone values are properly shown on a display. It should be only used with linearized (not gamma corrected) images that are normalized to the 0-1 range. .TP \fB--bit-depth\fR , \fB-b Enforce bit-depth (per color channel). The allowed values are from 8 to 32. If not specified, the bit depth from the pfs stream ('BITDEPTH' tag) will be used instead. If the 'BITDEPTH' tag is missing, the ImageMagick default value will be used (usually 16). The bit depth of actually stored file may be lower of that specified, if the file format does not support higher bit depths. .SH EXAMPLES .TP pfsin memorial.tiff | pfsoutrgbe memorial.hdr Converts from one HDR format to another .TP pfsin test.jpg | pfsout test.png Convert from one image format to another. .TP pfsin --linear test.jpg | pfsout --srgb test.png The same as above, but apply inverse gamma correction after reading an image (--linear) and then apply gamma correction before writing an image (--srgb). .SH "SEE ALSO" .BR pfsout (1) .BR pfsinppm (1) .SH BUGS Writing TIFF files with pfsouttiff seems to be broken, but pfsoutimgmagick can be used instead. pfsout will attempt to use pfsoutimgmagick and fall back to pfsouttiff if pfstools were not compiled with ImageMagick. Please report bugs and comments on implementation to the discussion group http://groups.google.com/group/pfstools pfstools-2.2.0/src/fileformat/pfsoutimgmagick.10000664000701400070140000000654614105165615020267 0ustar rkm38rkm38.TH "pfsoutimgmagick" 1 .SH NAME pfsoutimgmagick \- Write images or frames using Image Magick library .SH SYNOPSIS .B pfsoutimgmagick [--linear] [--quality ] [--bit-depth] [--frames ]) [...] .SH DESCRIPTION This command can write images or frames in the multitude of image formats supported by the ImageMagick library. This includes but is not limited to: BMP, JPG, PNG, GIF, EPS, TIFF, DPX. See the manual page of ImageMagick for the full list of available formats. .PP \fBpfsoutimgmagick\fR reads images or frames in \fIpfs\fR format from the Standard Input and writes them under the specified file name(s). .PP The input of \fBpfsinimgmagick\fR must be within 0-1 range. If \fILUMINANCE\fR tag set is set to \fIRELATIVE\fR or \fIABSOLUTE\fR, the linear values will be gamma corrected, assuming sRGB color space. Use \fI--linear\fR switch to disable gamma correction. .PP The images are stored using the bit-depth for which the ImageMagick library was compiled (default is 16 bit). Note that some formats (for example JPEG) do not support encoding more than 8 bits per color channel. To use higher bit-depths, store images in the PNG format. .PP This command stores 'ALPHA' channel if it is present in the pfs stream. .PP Each file name can contain a \%%d pattern, which is substituted with frame numbers. The pattern has the same syntax as C \fIprintf\fR command. For example, you can use \%%04d to make the frame number four digit with proceedings zeros. You can select the frames using the following options (the options must be always given after the file name): .TP .B \--frames Range is given in mathlab / octave format: .B "startframe:step:endframe" Frame numbers start with .B "startframe" (default 0), are increased by .B "step" (default 1) and stop at .B "endframe" You can skip one of those values, for example .I "1:100" for frames 1,2,...,100 and .I 0:2: for frame 0,2,4,... up to the last file that exists. .TP .B --srgb, -s Apply the sRGB non-linearity (approximately 2.2 gamma) before writing a file. This can be used instead of using \fIpfsgamma -g 2.2\fR to store the result of some tone-mapping operators. The option will compress contrast to make sure that tone values are properly shown on a display. It should be only used with linearized (not gamma corrected) images that are normalized to the 0-1 range. .TP \fB--quality\fR , \fB-q\fR Set JPEG/PNG compression level from 1 to 100. Default 75. .TP \fB--bit-depth\fR , \fB-b Enforce bit-depth (per color channel). The allowed values are from 8 to 32. If not specified, the bit depth from the pfs stream ('BITDEPTH' tag) will be used instead. If the 'BITDEPTH' tag is missing, the ImageMagick default value will be used (usually 16). The bit depth of actually stored file may be lower of that specified, if the file format does not support higher bit depths. .SH EXAMPLES .TP pfsin test.jpg | pfsout test.png Convert from one image format to another. .TP pfsin --linear test.jpg | pfsout --srgb test.png The same as above, but apply inverse gamma correction after reading an image (--linear) and then apply gamma correction before writing an image (--srgb). .SH BUGS pfsoutimgmagick can not write frames to the standard output. .PP Please report bugs and comments on implementation to the discussion group http://groups.google.com/group/pfstools .SH "SEE ALSO" .BR pfsout (1) .BR ImageMagick (1) pfstools-2.2.0/src/fileformat/pfsintiff.10000664000701400070140000000450614105165615017060 0ustar rkm38rkm38.TH "pfsintiff" 1 .SH NAME pfsintiff \- Load images or frames in several variants of TIFF format (both LDR and HDR) .SH SYNOPSIS .B pfsintiff ( [--linear] [--frames ] [--skip-missing]) [...] .SH DESCRIPTION .I pfsintiff command loads images in TIFF format and writes \fIpfs\fR stream to the Standard Output. The \fIpfs\fR stream is usually piped to another program for further processing. To detect the format automatically based on the extension, use \fIpfsin\fR command. .PP Both the standard LDR tiff and extended HDR (logLuv encoding http://www.anyhere.com/gward/pixformat/tiffluv.html); Note that both LDR and HDR tiff files provide photometric data (ie linearly related to luminance). The 'LUMINANCE' tag is set to 'RELATIVE', also the '--linear' switch is ignored. .PP To read images from standard input use a single dash '-' instead of filename. The images are read until EOF is reached. .PP Each file can contain a \%%d pattern, which is substituted with frame numbers. The pattern has the same syntax as C .I printf command. For example, you can use \%%04d to make the frame number four digit with proceedings zeros. You can select the frames using the following options (the options must be always given after the file name): .TP .B \--frames Range is given in mathlab / octave format: .B "startframe:step:endframe" Frame numbers start with .B "startframe" (default 0), are increased by .B "step" (default 1) and stop at .B "endframe" You can skip one of those values, for example .I "1:100" for frames 1,2,...,100 and .I 0:2: for frame 0,2,4,... up to the last file that exists. .TP .B \--skip-missing Skip up to ten frames in a row if corresponding files are missing. Otherwise the program stops reading sequence at the first file that does not exists. This switch does not apply to the first frame in a sequence. This switch can be useful if there is a rendered animation where some of the frame has not been generated. .TP .B \--linear Ignored for compatibility with \fIpfsinppm\fR. .SH EXAMPLES .TP pfsintiff frame\%%04d.tif \--frames 0:10 | pfsview Read frames from files frame0000.tif, frame0001.tif, ..., frame0010.tif and show them using pfsview. .SH BUGS Please report bugs and comments on implementation to the discussion group http://groups.google.com/group/pfstools .SH "SEE ALSO" .BR pfsin (1), .BR pfsout (1) pfstools-2.2.0/src/fileformat/pfsoutppm.cpp0000664000701400070140000001214014105165615017540 0ustar rkm38rkm38/** * @file * @brief Writing ppm frames * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Rafal Mantiuk and Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Rafal Mantiuk, * * $Id: pfsoutppm.cpp,v 1.5 2009/05/13 17:26:14 rafm Exp $ */ #include #include #include #include #include #include "ppmio.h" #define PROG_NAME "pfsoutppm" class QuietException { }; void printHelp() { std::cerr << PROG_NAME " [--linear] [--verbose] [--help]" << std::endl << "See man page for more information." << std::endl; } void writeFrames( int argc, char* argv[] ) { pfs::DOMIO pfsio; bool verbose = false; bool opt_srgb=false; int opt_bit_depth = -1; // Parse command line parameters static struct option cmdLineOptions[] = { { "help", no_argument, NULL, 'h' }, { "verbose", no_argument, NULL, 'v' }, { "srgb", no_argument, NULL, 's' }, { "bit-depth", required_argument, NULL, 'b' }, { NULL, 0, NULL, 0 } }; static const char optstring[] = "shvb:"; pfs::FrameFileIterator it( argc, argv, "wb", NULL, stdout, optstring, cmdLineOptions ); int optionIndex = 0; while( 1 ) { int c = getopt_long (argc, argv, optstring, cmdLineOptions, &optionIndex); if( c == -1 ) break; switch( c ) { case 'h': printHelp(); throw QuietException(); case 'v': verbose = true; break; case 'b': opt_bit_depth = strtol( optarg, NULL, 10 ); break; case 's': opt_srgb = true; break; case '?': throw QuietException(); case ':': throw QuietException(); } } if( opt_bit_depth!=-1 && (opt_bit_depth < 8 || opt_bit_depth > 32) ) throw pfs::Exception( "'bit-depth' argument must be within 8-32 range" ); while( true ) { pfs::Frame *frame = pfsio.readFrame( stdin ); if( frame == NULL ) { break; } pfs::FrameFile ff = it.getNextFrameFile(); if( ff.fh == NULL ) { pfsio.freeFrame( frame ); break; // No more frames } int bitdepth = 8; if( opt_bit_depth != -1 ) bitdepth = opt_bit_depth; else { const char* bitDepthTag = frame->getTags()->getString("BITDEPTH"); if( bitDepthTag!=NULL ) { bitdepth=strtol( bitDepthTag, NULL, 10 ); if( bitdepth < 8 ) bitdepth = 8; else if( bitdepth > 16 ) bitdepth = 16; } } PPMWriter writer( PROG_NAME, ff.fh, bitdepth ); pfs::Channel *X, *Y, *Z; frame->getXYZChannels( X, Y, Z ); if( X == NULL ) // No color { Y = frame->getChannel( "Y" ); if( Y == NULL ) // No luminance throw pfs::Exception( "Missing X, Y, Z channels in the PFS stream" ); // Grey levels X = frame->createChannel( "X" ); Z = frame->createChannel( "Z" ); pfs::transformColorSpace( pfs::CS_RGB, Y, Y, Y, pfs::CS_XYZ, X, Y, Z ); } const char* luminanceTag = frame->getTags()->getString("LUMINANCE"); if( luminanceTag!=NULL && strcmp(luminanceTag,"ABSOLUTE")==0 ) std::cerr << PROG_NAME << " warning: This file format cannot store absolute luminance values\n"; if( opt_srgb ) { if( luminanceTag!=NULL && strcmp(luminanceTag,"DISPLAY")==0 ) std::cerr << PROG_NAME << " warning: This image seems to be display referred thus there is no need for applying the sRGB non-linearity\n"; pfs::transformColorSpace( pfs::CS_XYZ, X, Y, Z, pfs::CS_SRGB, X, Y, Z ); VERBOSE_STR << "writing file (sRGB corrected) '" << ff.fileName << "'" << std::endl; } else { pfs::transformColorSpace( pfs::CS_XYZ, X, Y, Z, pfs::CS_RGB, X, Y, Z ); VERBOSE_STR << "writing file (linear) '" << ff.fileName << "'" << std::endl; } writer.writeImage( X, Y, Z ); pfsio.freeFrame( frame ); it.closeFrameFile( ff ); } } int main( int argc, char* argv[] ) { try { writeFrames( argc, argv ); } catch( pfs::Exception ex ) { std::cerr << PROG_NAME << " error: " << ex.getMessage() << std::endl; return EXIT_FAILURE; } catch( QuietException ex ) { return EXIT_FAILURE; } return EXIT_SUCCESS; } pfstools-2.2.0/src/fileformat/pfsout.10000664000701400070140000000310314105165615016400 0ustar rkm38rkm38.TH "pfsout" 1 .SH NAME pfsout \- Read pfs frames from stdin and write them in the format determined by the extension of the file name .SH SYNOPSIS .B pfsout pfsout [...] .SH DESCRIPTION This command can be used to write pfs frames piped to standard input in one of the several recognized formats. The proper format is determined by the extension of the give file name. To get a list of recognized formats and extensions, execute: pfsin --help This command is a front-end for pfsout* programs for writing images: pfsoutrgbe, pfsoutexr, etc. Based on the file extension, appropriate program is executed. If two different file format are given as parameters, two different program for writing images are executed. Additional options starting with dash '-' can be passed to pfsout* programs. The following rules apply for passing the options: the options given before any image file name (or %d pattern) are passed to all pfsout* programs. Options given after image file name are only passed to the program executed for that file(s). Note also that all option that take an argument (except \fB--frames\fR) must given in the form \fI--option=value\fR, that is without a space between an option and its argument. .SH EXAMPLES .TP pfsin memorial.tiff | pfsout memorial.hdr Converts from one HDR format to another .SH "SEE ALSO" .BR pfsin (1) .SH BUGS This command currently does not handle multiple frames given with a \%%d pattern in case of LDR formats: JPEG, PNG, PNM. Please report bugs and comments on implementation to the discussion group http://groups.google.com/group/pfstools pfstools-2.2.0/src/fileformat/ppmio.cpp0000664000701400070140000000666114105165615016642 0ustar rkm38rkm38/** * @brief * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Rafal Mantiuk and Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Rafal Mantiuk, * * $Id: ppmio.cpp,v 1.5 2009/05/25 19:24:49 rafm Exp $ */ #include "ppmio.h" extern "C" { #include } #include #include struct PPMData { pixval maxPV; int formatP; }; static inline float clamp( const float v, const float minV, const float maxV ) { if( v < minV ) return minV; if( v > maxV ) return maxV; return v; } PPMReader::PPMReader( const char *program_name, FILE *fh ) : fh(fh) { pm_init(program_name, 0); data = new PPMData; ppm_readppminit( fh, &width, &height, &data->maxPV, &data->formatP ); } bool PPMReader::readImage( pfs::Array2D *X, pfs::Array2D *Y, pfs::Array2D *Z ) { pixel *ppmRow; ppmRow = ppm_allocrow( width ); assert( ppmRow != NULL ); float normalization_factor = 1.f / (float)data->maxPV; for( int y = 0; y < height; y++ ) { // For each row of the image ppm_readppmrow( fh, ppmRow, width, data->maxPV, data->formatP ); for( int x = 0; x < width; x++ ) { (*X)(x,y) = (float)PPM_GETR(ppmRow[x]) * normalization_factor; (*Y)(x,y) = (float)PPM_GETG(ppmRow[x]) * normalization_factor; (*Z)(x,y) = (float)PPM_GETB(ppmRow[x]) * normalization_factor; } } ppm_freerow( ppmRow ); int eofP; ppm_nextimage( fh, &eofP); return eofP!=0; } int PPMReader::getBitDepth() { return (int)ceil( log2( (float)data->maxPV ) ); } PPMReader::~PPMReader() { delete data; } PPMWriter::PPMWriter( const char *program_name, FILE *fh, int bit_depth ) : fh(fh), bit_depth( bit_depth ) { pm_init(program_name, 0); } void PPMWriter::writeImage( pfs::Array2D *X, pfs::Array2D *Y, pfs::Array2D *Z ) { pixel *ppmRow; int width = X->getCols(); int height = X->getRows(); ppmRow = ppm_allocrow( width ); assert( ppmRow != NULL ); const int max_val = (1<...] .SH DESCRIPTION This command can load images or frames in the multitude of data formats supported by GDAL (Geospatial Data Abstraction Library). See \fIhttp://www.gdal.org/formats_list.html\fR for a list of supported formats. .PP \fBpfsingdal\fR reads images or frames from files and writes \fIpfs\fR stream to the Standard Output. The \fIpfs\fR stream is usually piped to another program for further processing. .PP The output of \fBpfsingdal\fR is the closest possible match of the input data. .SH EXAMPLES .TP pfsingdal sardata.cosar | pfsview .SH NOTES pfsingdal can not read from pipes, due to the design of GDAL. .PP Please report bugs and comments to Martin Lambers . .SH "SEE ALSO" .BR pfsin (1), .BR pfsout (1) pfstools-2.2.0/src/fileformat/CMakeLists.txt0000664000701400070140000001262214105165615017544 0ustar rkm38rkm38include_directories ("${PROJECT_BINARY_DIR}/" "${PROJECT_SOURCE_DIR}/src/pfs") if( NOT HAS_GETOPT ) include_directories ("${GETOPT_INCLUDE}") endif( NOT HAS_GETOPT ) link_directories("${PROJECT_SOURCE_DIR}/src/pfs") set( SHELL_CMDS pfsin pfsout pfsindcraw ) #set( SHELL_CMDS ) foreach(SHCMD ${SHELL_CMDS}) # Replace the tag with the path to bash add_custom_command( OUTPUT ${SHCMD} DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${SHCMD}.in COMMAND sed -e "'s;@BASH_PATH@;${BASH_EXECUTABLE};g'" ${CMAKE_CURRENT_SOURCE_DIR}/${SHCMD}.in >${CMAKE_CURRENT_BINARY_DIR}/${SHCMD} ) add_custom_target(${SHCMD}_sh ALL DEPENDS ${SHCMD}) endforeach(SHCMD) #file(READ ${CMAKE_CURRENT_SOURCE_DIR}/pfsin.in file_content) #string(REGEX REPLACE "@BASH_PATH@" "${BASH_EXECUTABLE}" file_content "${file_content}") #file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/pfsin" "${file_content}") #file(READ ${CMAKE_CURRENT_SOURCE_DIR}/pfsout.in file_content) #string(REGEX REPLACE "(@BASH_PATH@)" "${BASH_EXECUTABLE}" file_content "${file_content}") #file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/pfsout" "${file_content}") #file(READ ${CMAKE_CURRENT_SOURCE_DIR}/pfsindcraw.in file_content) #string(REGEX REPLACE "(@BASH_PATH@)" "${BASH_EXECUTABLE}" file_content "${file_content}") #file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/pfsindcraw" "${file_content}") install (FILES "${CMAKE_CURRENT_BINARY_DIR}/pfsin" "${CMAKE_CURRENT_BINARY_DIR}/pfsout" "${CMAKE_CURRENT_BINARY_DIR}/pfsindcraw" PERMISSIONS OWNER_EXECUTE GROUP_EXECUTE WORLD_EXECUTE OWNER_WRITE WORLD_READ GROUP_READ OWNER_READ DESTINATION bin) install (FILES pfsin.1 DESTINATION ${MAN_DIR}) install (FILES pfsindcraw.1 DESTINATION ${MAN_DIR}) install (FILES pfsout.1 DESTINATION ${MAN_DIR}) add_executable(pfsinyuv pfsinyuv.cpp "${GETOPT_OBJECT}") target_link_libraries(pfsinyuv pfs) install (TARGETS pfsinyuv DESTINATION bin) install (FILES pfsinyuv.1 DESTINATION ${MAN_DIR}) add_executable(pfsoutyuv pfsoutyuv.cpp "${GETOPT_OBJECT}") target_link_libraries(pfsoutyuv pfs) install (TARGETS pfsoutyuv DESTINATION bin) install (FILES pfsoutyuv.1 DESTINATION ${MAN_DIR}) add_executable(pfsinrgbe pfsinrgbe.cpp rgbeio.cpp rgbeio.h "${GETOPT_OBJECT}") target_link_libraries(pfsinrgbe pfs) install (TARGETS pfsinrgbe DESTINATION bin) install (FILES pfsinrgbe.1 DESTINATION ${MAN_DIR}) add_executable(pfsoutrgbe pfsoutrgbe.cpp rgbeio.cpp rgbeio.h "${GETOPT_OBJECT}") target_link_libraries(pfsoutrgbe pfs) install (TARGETS pfsoutrgbe DESTINATION bin) install (FILES pfsoutrgbe.1 DESTINATION ${MAN_DIR}) add_executable(pfsinpfm pfsinpfm.cpp "${GETOPT_OBJECT}") target_link_libraries(pfsinpfm pfs) install (TARGETS pfsinpfm DESTINATION bin) install (FILES pfsinpfm.1 DESTINATION ${MAN_DIR}) add_executable(pfsoutpfm pfsoutpfm.cpp "${GETOPT_OBJECT}") target_link_libraries(pfsoutpfm pfs) install (TARGETS pfsoutpfm DESTINATION bin) install (FILES pfsoutpfm.1 DESTINATION ${MAN_DIR}) if( OPENEXR_FOUND ) include_directories("${OPENEXR_INCLUDE_DIR}") add_executable(pfsinexr pfsinexr.cpp "${GETOPT_OBJECT}") target_link_libraries(pfsinexr pfs ${OPENEXR_LIBRARIES}) install (TARGETS pfsinexr DESTINATION bin) install (FILES pfsinexr.1 DESTINATION ${MAN_DIR}) add_executable(pfsoutexr pfsoutexr.cpp "${GETOPT_OBJECT}") target_link_libraries(pfsoutexr pfs ${OPENEXR_LIBRARIES}) install (TARGETS pfsoutexr DESTINATION bin) install (FILES pfsoutexr.1 DESTINATION ${MAN_DIR}) endif( OPENEXR_FOUND ) if( ImageMagick_FOUND ) include_directories(${ImageMagick_INCLUDE_DIRS}) add_executable(pfsinimgmagick pfsinimgmagick.cpp "${GETOPT_OBJECT}") target_link_libraries(pfsinimgmagick pfs ${ImageMagick_LIBRARIES}) install (TARGETS pfsinimgmagick DESTINATION bin) install (FILES pfsinimgmagick.1 DESTINATION ${MAN_DIR}) set_target_properties(pfsinimgmagick PROPERTIES COMPILE_FLAGS "-DMAGICKCORE_HDRI_ENABLE=0 -DMAGICKCORE_QUANTUM_DEPTH=16") add_executable(pfsoutimgmagick pfsoutimgmagick.cpp "${GETOPT_OBJECT}") target_link_libraries(pfsoutimgmagick pfs ${ImageMagick_LIBRARIES}) install (TARGETS pfsoutimgmagick DESTINATION bin) install (FILES pfsoutimgmagick.1 DESTINATION ${MAN_DIR}) set_target_properties(pfsoutimgmagick PROPERTIES COMPILE_FLAGS "-DMAGICKCORE_HDRI_ENABLE=0 -DMAGICKCORE_QUANTUM_DEPTH=16") endif( ImageMagick_FOUND ) if( NETPBM_FOUND ) include_directories(${NETPBM_INCLUDE_DIR}) add_executable(pfsinppm pfsinppm.cpp ppmio.cpp "${GETOPT_OBJECT}") target_link_libraries(pfsinppm pfs ${NETPBM_LIBRARIES}) install (TARGETS pfsinppm DESTINATION bin) install (FILES pfsinppm.1 DESTINATION ${MAN_DIR}) add_executable(pfsoutppm pfsoutppm.cpp ppmio.cpp "${GETOPT_OBJECT}") target_link_libraries(pfsoutppm pfs ${NETPBM_LIBRARIES}) install (TARGETS pfsoutppm DESTINATION bin) install (FILES pfsoutppm.1 DESTINATION ${MAN_DIR}) endif( NETPBM_FOUND ) if( TIFF_FOUND ) include_directories(${TIFF_INCLUDE_DIR}) add_executable(pfsintiff pfsintiff.cpp hdrtiffio.cpp "${GETOPT_OBJECT}") target_link_libraries(pfsintiff pfs ${TIFF_LIBRARY}) install (TARGETS pfsintiff DESTINATION bin) install (FILES pfsintiff.1 DESTINATION ${MAN_DIR}) add_executable(pfsouttiff pfsouttiff.cpp hdrtiffio.cpp "${GETOPT_OBJECT}") target_link_libraries(pfsouttiff pfs ${TIFF_LIBRARY}) install (TARGETS pfsouttiff DESTINATION bin) install (FILES pfsoutppm.1 DESTINATION ${MAN_DIR} RENAME pfsouttiff.1) endif( TIFF_FOUND ) pfstools-2.2.0/src/fileformat/pfsinimgmagick.10000664000701400070140000000670514105165615020063 0ustar rkm38rkm38.TH "pfsinimgmagick" 1 .SH NAME pfsinimgmagick \- Load images or frames using ImageMagick++ library .SH SYNOPSIS .B pfsinimgmagick ( [--linear] [--absolute ] [--frames ] [--skip-missing]) [...] .SH DESCRIPTION This command can load images or frames in the multitude of image formats supported by the ImageMagick library. This includes but is not limited to: BMP, JPG, PNG, GIF, EPS, TIFF, DPX. See the manual page of ImageMagick for the full list of available formats. .PP \fBpfsinimgmagick\fR reads images or frames from files and writes \fIpfs\fR stream to the Standard Output. The \fIpfs\fR stream is usually piped to another program for further processing. To detect the format automatically based on the extension, use \fIpfsin\fR command. .PP The output of \fBpfsinimgmagick\fR is scaled to 0-1 range, unless '--absolute' option is specified. By default, the 'LUMINANCE' tag is set to 'DISPLAY' contents. Using the '--linear' switch you can force the inverse sRGB transformation to provide linear data. In this case the 'LUMINANCE' tag is set to 'RELATIVE' contents. '--absolute' switch can be used to convert pixels to absolute luminance values. .PP This command handles properly 'alpha' channel (transparency) stored in images. .PP Each file can contain a \%%d pattern, which is substituted with frame numbers. The pattern has the same syntax as C .I printf command. For example, you can use \%%04d to make the frame number four digit with proceedings zeros. You can select the frames using the following options (the options must be always given after the file name): .TP .B \--frames Range is given in mathlab / octave format: .B "startframe:step:endframe" Frame numbers start with .B "startframe" (default 0), are increased by .B "step" (default 1) and stop at .B "endframe" You can skip one of those values, for example .I "1:100" for frames 1,2,...,100 and .I 0:2: for frame 0,2,4,... up to the last file that exists. .TP .B \--skip-missing Skip up to ten frames in a row if corresponding files are missing. Otherwise the program stops reading sequence at the first file that does not exists. This switch does not apply to the first frame in a sequence. This switch can be useful if there is a rendered animation where some of the frame has not been generated. .TP .B \--linear, -l Converts pixel values to linear luminance (XYZ), assuming the sRGB color space for the input image. The maximum pixel value (255,255,255) is mapped to Y=1. \fILUMINANCE\fR tag is set to RELATIVE. .TP .B \--absolute , -a \fB--absolute\fR converts pixel values to an absolute linear luminance (XYZ), that is the color space, in which channel Y contains luminance given in cd/m^2. The sRGB color space is assumed for the input image. The maximum pixel value (255,255,255) is mapped to Y=\fI\fR. \fI\fR is typically set to 80 [cd/m^2] for a CRT monitor. \fILUMINANCE\fR tag is set to ABSOLUTE. \fB--absolute\fR process images almost the same as \fB--relative\fR, but additionally it scales all pixels by \fI\fR. .SH EXAMPLES .TP pfsinimgmagick frame\%%04d.dpx \--frames 0:10 | pfsview Read frames from files frame0000.dpx, frame0001.dpx, ..., frame0010.dpx and show them using pfsview. .SH BUGS pfsinimgmagick can not read frames from the Standard Input. .PP Please report bugs and comments on implementation to the discussion group http://groups.google.com/group/pfstools .SH "SEE ALSO" .BR pfsin (1), .BR pfsout (1) .BR ImageMagick (1) pfstools-2.2.0/src/fileformat/pfsinpfm.cpp0000664000701400070140000001570414105165615017336 0ustar rkm38rkm38/** * @brief Read files in PFM format * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Rafal Mantiuk and Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Rafal Mantiuk, * * The format description was based on: * http://netpbm.sourceforge.net/doc/pfm.html * * $Id: pfsinpfm.cpp,v 1.1 2005/06/15 13:36:54 rafm Exp $ */ #include #include #include #include #include #include #include #include #define PROG_NAME "pfsinpfm" struct PFMHeader { int width, height; bool grayscale; float scale; bool need_swap; }; static inline uint32_t pfs_bswap_32(uint32_t x) { x= ((x<<8)&0xFF00FF00) | ((x>>8)&0x00FF00FF); x= (x>>16) | (x<<16); return x; } static float swap_if_needed(float x, bool swap_needed ) { if( swap_needed ) { uint32_t *xi = (uint32_t*)&x; uint32_t yi = pfs_bswap_32( *xi ); float *y = (float*)&yi; return *y; } else return x; } static bool isBigEndian() { int x = 1; char *y = (char*)&x; return y[0] == 0; } PFMHeader readPFMHeader( FILE *fh ) { PFMHeader header; char headerID[2]; int read; read = fscanf( fh, "%c%c\n%d %d\n%f", &headerID[0], &headerID[1], &header.width, &header.height, &header.scale ); if( read != 5 ) throw pfs::Exception( "Wrong file header" ); if( !memcmp( "PF", headerID, 2 ) ) header.grayscale = false; else if( !memcmp( "Pf", headerID, 2 ) ) header.grayscale = true; else throw pfs::Exception( "Wrong PFM image type" ); read = fread( headerID, 1, 1, fh ); // Read a single EOL character if( read != 1 || headerID[0] != 0x0a ) throw pfs::Exception( "Wrong file header" ); // Check if swapping bytes because of endianness is required header.need_swap = (isBigEndian() ^ (header.scale > 0)); return header; } void readPFMFileColor( FILE *fh, PFMHeader &header, float *R, float *G, float *B ) { const int lineSize = header.width*3; float *line = new float[lineSize]; // PFM images are stored in rows bottom to top for( int l = header.height-1; l >= 0; l-- ) { int read = fread( line, sizeof( float ), lineSize, fh ); if( read != lineSize ) throw pfs::Exception( "Unexpected EOF" ); for( int x = 0; x < header.width; x++ ) { const int lineOffset = l*header.width; R[lineOffset+x] = swap_if_needed( line[x*3+0], header.need_swap ); G[lineOffset+x] = swap_if_needed( line[x*3+1], header.need_swap ); B[lineOffset+x] = swap_if_needed( line[x*3+2], header.need_swap ); } } delete[] line; if( fabs(header.scale) != 1 ) { const float scaleFactor = fabs( header.scale ); for( int i = 0; i < header.width*header.height; i++ ) { R[i] *= scaleFactor; G[i] *= scaleFactor; B[i] *= scaleFactor; } } } void readPFMFileGrayscale( FILE *fh, PFMHeader &header, float *Y ) { // PFM images are stored in rows bottom to top for( int l = header.height-1; l >= 0; l-- ) { int read = fread( Y + header.width*l, sizeof( float ), header.width, fh ); if( header.need_swap ) { const int lineOffset = l*header.width; for( int x = 0; x < header.width; x++ ) { Y[lineOffset+x] = swap_if_needed( Y[lineOffset+x], 1 ); } } if( read != header.width ) throw pfs::Exception( "Unexpected EOF" ); } if( fabs(header.scale) != 1 ) { const float scaleFactor = fabs( header.scale ); for( int i = 0; i < header.width*header.height; i++ ) { Y[i] *= scaleFactor; } } } // ======== Main class QuietException { }; void printHelp() { fprintf( stderr, PROG_NAME " [--verbose] [--help]\n" "See man page for more information.\n" ); } void readFrames( int argc, char* argv[] ) { pfs::DOMIO pfsio; bool verbose = false; // Parse command line parameters static struct option cmdLineOptions[] = { { "help", no_argument, NULL, 'h' }, { "verbose", no_argument, NULL, 'v' }, { "linear", no_argument, NULL, 'l' }, { NULL, 0, NULL, 0 } }; static const char optstring[] = "hvl"; pfs::FrameFileIterator it( argc, argv, "rb", NULL, stdin, optstring, cmdLineOptions ); int optionIndex = 0; while( 1 ) { int c = getopt_long (argc, argv, optstring, cmdLineOptions, &optionIndex); if( c == -1 ) break; switch( c ) { case 'h': printHelp(); throw QuietException(); case 'v': verbose = true; break; case 'l': std::cerr << PROG_NAME << " warning: linearize option ignored for an HDR input!" << std::endl; break; case '?': throw QuietException(); case ':': throw QuietException(); } } while( true ) { pfs::FrameFile ff = it.getNextFrameFile(); if( ff.fh == NULL ) break; // No more frames PFMHeader header = readPFMHeader( ff.fh ); VERBOSE_STR << "reading file '" << ff.fileName << "'" << std::endl; VERBOSE_STR << "PFM file endianness: " << (header.scale > 0 ? "Big" : "Little" ) << std::endl; pfs::Frame *frame = pfsio.createFrame( header.width, header.height ); if( header.grayscale ) { pfs::Channel *Y; Y = frame->createChannel( "Y" ); readPFMFileGrayscale( ff.fh, header, Y->getRawData() ); } else { pfs::Channel *X, *Y, *Z; frame->createXYZChannels( X, Y, Z ); readPFMFileColor( ff.fh, header, X->getRawData(), Y->getRawData(), Z->getRawData() ); pfs::transformColorSpace( pfs::CS_RGB, X, Y, Z, pfs::CS_XYZ, X, Y, Z ); } it.closeFrameFile( ff ); frame->getTags()->setString("LUMINANCE", "RELATIVE"); const char *fileNameTag = strcmp( "-", ff.fileName )==0 ? "stdin" : ff.fileName; frame->getTags()->setString( "FILE_NAME", fileNameTag ); pfsio.writeFrame( frame, stdout ); pfsio.freeFrame( frame ); } } int main( int argc, char* argv[] ) { try { readFrames( argc, argv ); } catch( pfs::Exception ex ) { fprintf( stderr, PROG_NAME " error: %s\n", ex.getMessage() ); return EXIT_FAILURE; } catch( QuietException ex ) { return EXIT_FAILURE; } return EXIT_SUCCESS; } pfstools-2.2.0/src/fileformat/pfsoutpfm.10000664000701400070140000000202714105165615017107 0ustar rkm38rkm38.TH "pfsoutpfm" 1 .SH NAME pfsoutpfm \- Write images or frames in OpenEXR format .SH SYNOPSIS .B pfsoutpfm ( [--frames ]) [...] .SH DESCRIPTION Use this command to write frames in PFM format. PFM format is used for HDR Shop plug-ins and is also supported by some recent versions of NetPBM package (pamtopfm and pfmtopam commands). Source pfs frames should be piped to the Standard Input. This command can handle both color (XYZ channels) and grayscale (Y channel) images. Details on the format of the pattern file names, which are used for saving multiple frames, can be found in the manual page of pfsoutppm. .SH EXAMPLES .TP pfsin memorial.hdr | pfsoutpfm memorial.pfm Converts from one HDR format to another .TP pfsin memorial.hdr | pfsextractchannels Y | pfsoutpfm memorial.pfm Stores memorial image as a grayscale PFM file. .SH "SEE ALSO" .BR pfsout (1) .BR pfsoutppm (1) .BR pfsinpfm (1) .SH BUGS Please report bugs and comments on implementation to the discussion group http://groups.google.com/group/pfstools pfstools-2.2.0/src/fileformat/pfsin.in0000664000701400070140000001230314105165615016447 0ustar rkm38rkm38#!@BASH_PATH@ ############################################################ # Read any image and write pfs stream to the standard output ############################################################ if test -z "$1" || test "$1" = "--help"; then cat < [...] Recognized file formats and extensions: Radiance RGBE - .pic, .hdr TIFF (incl. LogLuv) - .tiff, .tif PNM, PPM - .ppm, .pnm JPEG - .jpeg, .jpg PNG - .png PFS - .pfs OpenEXR - .exr PFM - .pfm DPX - .dpx GIF - .gif BMP - .bmp EPS - .eps hdrgen - .hdrgen (multi-exposure sequence, see pfscalibration) Canon 350D RAW - .cr2 (and other camera RAW formats recognized by dcraw) See the man page for more information. EOF exit 1 fi #Arguments used for all images passed to pfsout global_arguments="" if test -n "$1"; then while test "${1:0:1}" = "-"; do #Handle options that require a parameter for par_opt in "--frames" "-f" "--absolute" "-a"; do if test "$1" = $par_opt; then if test -z "$2"; then echo >&2 "Required parameter missing after $par_opt" exit 1; fi global_arguments="$global_arguments $1" shift break; fi done global_arguments="$global_arguments $1" shift done fi while test "$1"; do extension="${1##*.}" file_pattern="$1" # Handle common arguments arguments extra_arguments=""; if test -n "$2"; then while test "${2:0:1}" = "-"; do #Handle options that require a parameter for par_opt in "--frames" "-f" "--absolute" "-a"; do if test "$2" = $par_opt; then if test -z "$3"; then echo >&2 "Required parameter missing after $par_opt" exit 1; fi extra_arguments="$extra_arguments $2" shift break; fi done extra_arguments="$extra_arguments $2" shift done fi case "$extension" in ("hdr"|"HDR"|"pic"|"PIC") pfsinrgbe "$file_pattern" $global_arguments $extra_arguments ;; ("ppm"|"PPM"|"pnm"|"PNM"|"pgm"|"PGM") pfsinppm "$file_pattern" $global_arguments $extra_arguments ;; ("tif"|"TIF"|"tiff"|"TIFF") ## Use internal pfsintiff if possible, if not - ImageMagick if which pfsintiff >/dev/null; then pfsintiff "$file_pattern" $global_arguments $extra_arguments elif which pfsinimgmagick >/dev/null; then pfsinimgmagick "$file_pattern" $global_arguments $extra_arguments else echo 1>&2 "$extension files unsupported. Compile pfstools with ImageMagick or libtiff." exit 1 fi ;; ("exr"|"EXR") pfsinexr "$file_pattern" $global_arguments $extra_arguments ;; ("pfm"|"PFM") pfsinpfm "$file_pattern" $global_arguments $extra_arguments ;; ("jpg"|"JPG"|"jpeg"|"JPEG") ## Use ImageMagick if possible, if not - netpbm if which pfsinimgmagick >/dev/null; then pfsinimgmagick "$file_pattern" $global_arguments $extra_arguments elif which jpegtopnm >/dev/null; then jpegtopnm "$file_pattern" | pfsinppm - $global_arguments $extra_arguments else echo 1>&2 "$extension files unsupported. Compile pfstools with ImageMagick or NetPBM." exit 1 fi ;; ("png"|"PNG") ## Use ImageMagick if possible, if not - netpbm if which pfsinimgmagick >/dev/null; then pfsinimgmagick "$file_pattern" $global_arguments $extra_arguments elif which pngtopnm >/dev/null; then pngtopnm "$file_pattern" | pfsinppm - $global_arguments $extra_arguments else echo 1>&2 "$extension files unsupported. Compile pfstools with ImageMagick or NetPBM." exit 1 fi ;; ("dpx"|"DPX"|"gif"|"GIF"|"bmp"|"BMP"|"eps"|"EPS") pfsinimgmagick "$file_pattern" $global_arguments $extra_arguments ;; ("pfs"|"PFS") cat "$file_pattern" | pfstag --set "FILE_NAME=${file_pattern}" ;; ("hdrgen"|"HDRGEN") pfsinhdrgen "$file_pattern" $global_arguments $extra_arguments ;; ("yuv"|"YUV") pfsinyuv "$file_pattern" $global_arguments $extra_arguments ;; (*) if dcraw -i "$file_pattern" > /dev/null 2>&1; then pfsindcraw "$file_pattern" $global_arguments $extra_arguments else echo 1>&2 "Unknown extension: $extension" exit 1 fi ;; esac shift done pfstools-2.2.0/src/fileformat/pfsinyuv.10000664000701400070140000000527614105165615016760 0ustar rkm38rkm38.TH "pfsinyuv" 1 .SH NAME pfsinyuv \- Read frames from a single .yuv file used by many video codecs .SH SYNOPSIS .B pfsinyuv [--verbose] [--quiet] [--width] [--height] [--colorspace] [--noguess] [--chroma-subsampling] [--bit-depth] [--frames] [--help] .SH DESCRIPTION Use this command to read an uncompressed yuv file into a pfsstream. .PP may contain one or more formatting strings, which describe the format of the yuv file. The formatting strings are: .TP .B %s The entire description in the format x___, for example 1920x1080_25_10b_pq2020_420 .TP .B %d Width in pixels .TP .B %d Height in pixels .TP .B %d Bit-depth in bits .TP .B %s Colorspace, such as 'bt709' .TP .B %s Chroma subsampling format, either '444' or '420' .SH OPTIONS .TP .B \--width, -w Specify the width in pixels of the YUV file .TP .B \--height, -h Specify the height in pixels of the YUV file .TP .B \--fps, -f Specify the frames per second of the YUV file .TP .B \--bit-depth, -b Bit-depth of the integer values stored in the yuv file. A single color component is stored as a byte if bit-depth is 8, or as two bytes otherwise. Default: 10 [bits] .TP .B \--chroma-format, -s Specify the chroma format of the YUV file. Can be one of: .IP .I 444 Standard 4:4:4 YCbCr format, with no chroma downsampling .P .IP .I 420 4:2:0 YCbCr format, with both colour channels downsampled by half .IP .I V210 The v210 format is a packed YUV 4:2:2 (UYVY) format with 10 bits per component .P .PP .B \--colorspace, -c Currently it can be either: .IP .I pq2020 for HDR images with BT.2020 colorspace and PQ transfer function, or .IP .I bt709 for LDR images in BT.709 (rec.709) colorspace without any transfer function or .IP .I hlg2020 for HDR images with BT.2020 colorspace and HLG transfer function. .PP If no option is specified, colorspace is inferred from the LUMINANNCE tag in the pfsstream: pq2020 if LUMINANCE is ABSOLUTE or RELATIVE, bt709 if LUMINANCE is DISPLAY. .TP .B \--no-guess, -n Don't try to extract image metadata from the filename .TP .B \--frames, -r Read only frames within the range specified as first:last, or first:step:last. The frames are indexed from 1, so that the to read the first and second frame one needs to specify 1:2. .TP .B \--verbose, -v Print extra information to stderr. .TP .B \--quiet, -q Disable warning message about the change in handling absolute values in RGBE files. See IMPORTANT NOTE below. .SH EXAMPLES .TP pfsinyuv video_1920x1080_10b_25fps_pq2020_444.yuv --frames 100:-1:0 -v | pfsview .SH "SEE ALSO" .BR pfsin (1) .SH IMPORTANT NOTE .SH BUGS Please report bugs and comments on implementation to the discussion group http://groups.google.com/group/pfstools pfstools-2.2.0/src/fileformat/pfsoutyuv.10000775000701400070140000000454714105165615017164 0ustar rkm38rkm38.TH "pfsoutyuv" 1 .SH NAME pfsoutyuv \- Write frames to .yuv file used by many video codecs .SH SYNOPSIS .B pfsoutyuv [--verbose] [--quiet] [--bitdepth] [--colorspace] [--downsample-filter] [--chroma-format] [...] .SH DESCRIPTION Use this command to write the pfsstream to an uncompressed .yuv file. .PP If the output color space is LDR (bt709), the input pfs stream should be in the gamma corrected sRGB color space. If the output color space is HDR (pq2020 or hgl2020), the input pfs stream should be in linear RGB color space. .SH OPTIONS .TP .B \--bitdepth, -b bit-depth of the integer values stored in the yuv file. a single color component is stored as a byte if bit-depth is 8, or as two bytes otherwise. default: 10 [bits] .TP .B \--colorspace, -c Currently it can be either: .IP .I pq2020 for HDR images with BT.2020 colorspace and PQ transfer function, or .IP .I hlg2020 for HDR images with BT.2020 colorspace and HLG transfer function, or .IP .I bt709 for LDR images in BT.709 (rec.709) colorspace without any transfer function. .PP If no option is specified, colorspace is inferred from the LUMINANCE tag in the pfsstream: pq2020 if LUMINANCE is ABSOLUTE or RELATIVE, bt709 if LUMINANCE is DISPLAY. .TP .B \--chroma-format, -f The sampling format for the chroma channels. Can be one of: .IP .I 444 The standard 4:4:4 format with no chroma downsampling. .IP .I 420 The 4:2:0 format with both colour channels being downsampled by half. .IP default: 420 .TP .B \--downsample-filter, -f The filter weights used when downsampling to 4:2:0. Can be either .IP .I f0 Weighting = [1/8, 3/4, 1/8] .IP .I f1 Weighting = [1/4, 1/2, 1/4] .IP default: f0 .TP .B \--srgb-input -s Set if the input is gamma corrected, though this will be inferred otherwise from the LUMINANCE tag in the pfs stream .TP .B \--linear-input -l As above, set if the input contains linear luminance values .TP .B \--verbose, -v Print extra information to stderr. .TP .B \--quiet, -q Disable warning message about the change in handling absolute values in RGBE files. See IMPORTANT NOTE below. .SH EXAMPLES .TP pfsin frame%04d.exr | pfsoutyuv video%s.yuv Converts a sequence of frames frame0000.exr, frame0001.exr, ... to a yuv file. .SH "SEE ALSO" .BR pfsin (1) .SH IMPORTANT NOTE .SH BUGS Please report bugs and comments on implementation to the discussion group http://groups.google.com/group/pfstools pfstools-2.2.0/src/fileformat/pfsinrgbe.10000664000701400070140000000721514105165615017047 0ustar rkm38rkm38.TH "pfsinrgbe" 1 .SH NAME pfsinrgbe \- Load images or frames in Radiance RGBE format .SH SYNOPSIS .B pfsinrgbe [--linear] ( [--frames ] [--skip-missing]) [...] .SH DESCRIPTION .I pfsinrgbe command loads images in Radiance RGBE format and writes \fIpfs\fR stream to the Standard Output. The \fIpfs\fR stream is usually piped to another program for further processing. To detect the format automatically based on the extension, use \fIpfsin\fR command. .PP Since RGBE is an HDR format, 'LUMINANCE' tag is set to 'RELATIVE' (relative linear luminance values). .PP To read images from standard input use a single dash '-' instead of filename. The images are read until EOF is reached. .PP Each file can contain a \%%d pattern, which is substituted with frame numbers. The pattern has the same syntax as C .I printf command. For example, you can use \%%04d to make the frame number four digit with proceedings zeros. You can select the frames using the following options (the options must be always given after the file name): .TP .B \--frames Range is given in mathlab / octave format: .B "startframe:step:endframe" Frame numbers start with .B "startframe" (default 0), are increased by .B "step" (default 1) and stop at .B "endframe" You can skip one of those values, for example .I "1:100" for frames 1,2,...,100 and .I 0:2: for frame 0,2,4,... up to the last file that exists. .TP .B \--skip-missing Skip up to ten frames in a row if corresponding files are missing. Otherwise the program stops reading sequence at the first file that does not exists. This switch does not apply to the first frame in a sequence. This switch can be useful if there is a rendered animation where some of the frame has not been generated. .TP .B \--linear Ignored for compatibility with \fIpfsinppm\fR. .TP .B \--radiance, -r Correct stored values so that the luminance is reported the same in pfsview and radiance tools (xview or Photospheare). See IMPORTANT NOTE below. .TP .B \--quiet, -q Disable warning message about the change in handling absolute values in RGBE files. See IMPORTANT NOTE below. .SH EXAMPLES .TP pfsinrgbe intro\%%d.hdr frame\%%d.hdr | ... Concatenate two animations into one and pass it to pipe for further processing. .SH IMPORTANT NOTE There is an incompatibility between viewers of RGBE format in terms of absolute luminance values. The ximage viewer from Radiance package expects radiance values in RGBE files and multiplies the resulting luminance by the WHITE_EFFICACY constant equal to 179.0f to convert from radiance to luminance. .PP To remain compatible with Radiance (xview and Photospheare), pfstools prior to 1.9.0 divided red, green and blue values by 179 on writing and multiplied by that constant on reading RGBE files. This in turn caused incompatibility with other software, such as Photoshop or HDR toolbox. Starting from pfstools 1.9.0 this division / multiplication step was removed to make pfstools compatible in terms of absolute values with the majority of the software. The downside is that this change made .hdr files created with the earlier versions of pfstools incompatible (in terms of absolute luminance units) with the pfstools 1.9.0 or later. To avoid any mistakes because of that change, a long warning messages is shown each time pfsinrgbe or pfsoutrgbe is called. The message can be suppressed with --quiet option. .PP To enable the old functionality and read or write RGBE files with the correction for WHITE_EFFICACY, add --radiance option to the command line. .SH BUGS Please report bugs and comments on implementation to the discussion group http://groups.google.com/group/pfstools .SH "SEE ALSO" .BR pfsin (1), .BR pfsout (1) pfstools-2.2.0/src/fileformat/pfsinppm.cpp0000664000701400070140000001234714105165615017350 0ustar rkm38rkm38/** * @brief Read files in PPM format * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Rafal Mantiuk and Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Rafal Mantiuk, * @author Grzegorz Krawczyk, * * $Id: pfsinppm.cpp,v 1.6 2009/05/13 17:26:14 rafm Exp $ */ #include #include #include #include #include #include "ppmio.h" #define PROG_NAME "pfsinppm" class QuietException { }; void printHelp() { std::cerr << PROG_NAME " [--linear] [--absolute ] [--verbose] [--help]" << std::endl << "See man page for more information." << std::endl; } void readFrames( int argc, char* argv[] ) { pfs::DOMIO pfsio; bool verbose = false; bool opt_linear=false; float absoluteMaxLum = 0; // Parse command line parameters static struct option cmdLineOptions[] = { { "help", no_argument, NULL, 'h' }, { "verbose", no_argument, NULL, 'v' }, { "linear", no_argument, NULL, 'l' }, { "absolute", required_argument, NULL, 'a' }, { NULL, 0, NULL, 0 } }; static const char optstring[] = "lhva:"; pfs::FrameFileIterator it( argc, argv, "rb", NULL, stdin, optstring, cmdLineOptions ); int optionIndex = 0; while( 1 ) { int c = getopt_long (argc, argv, optstring, cmdLineOptions, &optionIndex); if( c == -1 ) break; switch( c ) { case 'h': printHelp(); throw QuietException(); case 'v': verbose = true; break; case 'l': opt_linear = true; break; case 'a': absoluteMaxLum = (float)strtod( optarg, NULL ); break; case '?': throw QuietException(); case ':': throw QuietException(); } } if( absoluteMaxLum != 0 && opt_linear ) throw pfs::Exception( "'absolute' and 'linear' are conflicting options" ); if( absoluteMaxLum < 0 ) throw pfs::Exception( "maximum absolute luminance must be > 0" ); VERBOSE_STR << "linearize input image: " << ((opt_linear || absoluteMaxLum!=0) ? "yes" : "no") << std::endl; if( absoluteMaxLum != 0 ) VERBOSE_STR << "maximum absolute luminance: " << absoluteMaxLum << std::endl; while( true ) { pfs::FrameFile ff = it.getNextFrameFile(); if( ff.fh == NULL ) { break; // No more frames } PPMReader reader( PROG_NAME, ff.fh ); bool eofP = false; while( !eofP ) { VERBOSE_STR << "reading file '" << ff.fileName << "'" << std::endl; pfs::Frame *frame = pfsio.createFrame( reader.getWidth(), reader.getHeight() ); pfs::Channel *X, *Y, *Z; frame->createXYZChannels( X, Y, Z ); //Store sRGB data temporarily in XYZ channels eofP = reader.readImage( X, Y, Z ); if( opt_linear || absoluteMaxLum != 0 ) { pfs::transformColorSpace( pfs::CS_SRGB, X, Y, Z, pfs::CS_XYZ, X, Y, Z ); if( absoluteMaxLum != 0 ) { // Rescale to absolute luminance level const int pixCount = X->getWidth()*X->getHeight(); for( int i = 0; i < pixCount; i++ ) { (*X)(i) *= absoluteMaxLum; (*Y)(i) *= absoluteMaxLum; (*Z)(i) *= absoluteMaxLum; } frame->getTags()->setString("LUMINANCE", "ABSOLUTE"); } else frame->getTags()->setString("LUMINANCE", "RELATIVE"); } else { pfs::transformColorSpace( pfs::CS_RGB, X, Y, Z, pfs::CS_XYZ, X, Y, Z ); frame->getTags()->setString("LUMINANCE", "DISPLAY"); } // This is the luminance / luma perceived as reference white // Some tone-mappers may need this frame->getTags()->setString("WHITE_Y", "1"); char strbuf[3]; snprintf( strbuf, 3, "%d", reader.getBitDepth() ); frame->getTags()->setString("BITDEPTH", strbuf ); const char *fileNameTag = strcmp( "-", ff.fileName )==0 ? "stdin" : ff.fileName; frame->getTags()->setString( "FILE_NAME", fileNameTag ); pfsio.writeFrame( frame, stdout ); pfsio.freeFrame( frame ); it.closeFrameFile( ff ); } } } int main( int argc, char* argv[] ) { try { readFrames( argc, argv ); } catch( pfs::Exception ex ) { std::cerr << PROG_NAME << " error: " << ex.getMessage() << std::endl; return EXIT_FAILURE; } catch( QuietException ex ) { return EXIT_FAILURE; } return EXIT_SUCCESS; } pfstools-2.2.0/src/fileformat/pfsinexr.10000664000701400070140000000431014105165615016717 0ustar rkm38rkm38.TH "pfsinexr" 1 .SH NAME pfsinexr \- Load images or frames in OpenEXR format .SH SYNOPSIS .B pfsinexr [--keep-rgb] ( [--frames ] [--skip-missing]) [...] .SH DESCRIPTION Use this command to read frames in OpenEXR format. The frames are converted to pfs stream and send to the standard output. This command can read arbitrary channels from OpenEXR files; color channels XYZ are however specially handled (see option \fIkeep-rgb\fR). pfsinexr reads also all string attributes from OpenEXR file and write them as tags in pfs stream. Attributes of other types are ignored. If attribute name contains a colon (:), the part before colon is treated as a channel name and the attribute is assigned to that channel (similar notation as command line argument of pfstag program). pfsinexr recognizes OpenEXR standard attribute \fIWhiteLuminance\fR and multiplies the data by that value to get absolute luminance values (see also \fB--fix-halfmax\fR switch in pfsoutexr). Additionally the tag \fILUMINANCE\fR is set to \fIABSOLUTE\fR unless OpenEXR files contains attribute \fILUMINANCE\fR set to semething else. That is the channel \fIY\fR is assumed to represent absolute luminance levels in cd/m^2. If an OpenEXR file contains 'Z' channel, it is renamed to 'DEPTH' to avoid conflict with the Z color channel for the XYZ color space. Details on the format of the pattern file names, which are used for reading multiple frames, can be found in the manual page of pfsinppm. To automatically recognize a file format from the extension, use \fIpfsin\fR command instead. .TP .B \--keep-rgb By default, color channels R, G and B from an OpenEXR file are converted to XYZ color space, which is suggested format for color data in pfs. When \fIkeep-rgb\fR option is specified, color channels RGB are stored as they are without any conversion. .SH EXAMPLES .TP pfsin memorial.exr | pfsout memorial.hdr Converts from one HDR format to another .SH BUGS pfsinexr and pfsoutexr can not take stdin / stdout as an input/output (dash '-' instead of file name). Please report bugs and comments on implementation to the discussion group http://groups.google.com/group/pfstools .SH "SEE ALSO" .BR pfsin (1) .BR pfsinppm (1) .BR pfsoutexr (1) pfstools-2.2.0/src/fileformat/pfsoutffmpeg0000664000701400070140000000374614105165615017443 0ustar rkm38rkm38#!/bin/sh ############################################################ # Read pfs frames from stdin and forward them to ffmpeg # program ############################################################ # # This file is a part of PFSTOOLS package. # ---------------------------------------------------------------------- # Copyright (C) 2003,2004 Rafal Mantiuk and Grzegorz Krawczyk # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # ---------------------------------------------------------------------- # if test "$1" = "--help"; then cat < output_file.avi See manual of ffmpeg for further information. Note that the usage of '-f' switch (input format specifier) and '-i' switch (input file specifier) is implemented inside the script and is therefore not allowed. See the man page for sample usage. EOF exit 1 fi FFMPEG="/usr/bin/ffmpeg" FFMPEG_SWICHES="-f imagepipe -i - " FFMPEG_USER_SWICHES="" while test "$1"; do if test "$1" = "-f" -o "$1" = "-i"; then echo "pfsoutffmpeg: Usage of -f and -i switches is not allowed." &1>2 exit 1 fi FFMPEG_USER_SWICHES="${FFMPEG_USER_SWICHES} $1" shift done pfsoutppm - | ${FFMPEG} ${FFMPEG_SWICHES} ${FFMPEG_USER_SWICHES} pfstools-2.2.0/src/fileformat/pfsoutimgmagick.cpp0000664000701400070140000001557714105165615020715 0ustar rkm38rkm38/** * @file * @brief Writing frames using ImageMagick library * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Rafal Mantiuk and Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Rafal Mantiuk, * * $Id: pfsoutimgmagick.cpp,v 1.7 2011/03/15 08:47:24 rafm Exp $ */ #include #include #include #include #include #define PROG_NAME "pfsoutimgmagick" class QuietException { }; void printHelp() { std::cerr << PROG_NAME " [--linear] [--quality] [--bit-depth] [--verbose] [--help]" << std::endl << "See man page for more information." << std::endl; } template inline T clamp( const T v, const T minV, const T maxV ) { if( v < minV ) return minV; if( v > maxV ) return maxV; return v; } void writeFrames( int argc, char* argv[] ) { pfs::DOMIO pfsio; bool verbose = false; bool opt_srgb=false; int quality = 75; int opt_bit_depth = -1; // Parse command line parameters static struct option cmdLineOptions[] = { { "help", no_argument, NULL, 'h' }, { "verbose", no_argument, NULL, 'v' }, { "srgb", no_argument, NULL, 's' }, { "quality", required_argument, NULL, 'q' }, { "bit-depth", required_argument, NULL, 'b' }, { NULL, 0, NULL, 0 } }; static const char optstring[] = "shvq:b:"; pfs::FrameFileIterator it( argc, argv, "wb", NULL, NULL, optstring, cmdLineOptions ); int optionIndex = 0; while( 1 ) { int c = getopt_long (argc, argv, optstring, cmdLineOptions, &optionIndex); if( c == -1 ) break; switch( c ) { case 'h': printHelp(); throw QuietException(); case 'v': verbose = true; break; case 'q': quality = strtol( optarg, NULL, 10 ); break; case 'b': opt_bit_depth = strtol( optarg, NULL, 10 ); break; case 's': opt_srgb = true; break; case '?': throw QuietException(); case ':': throw QuietException(); } } if( quality < 1 || quality > 100 ) throw pfs::Exception( "'quality' argument must be within 1-100 range" ); if( opt_bit_depth != -1 && (opt_bit_depth < 8 || opt_bit_depth > 32 ) ) throw pfs::Exception( "'bit-depth' argument must be within 8-32 range" ); VERBOSE_STR << "quality: " << quality << "%\n"; Magick::InitializeMagick(""); while( true ) { pfs::Frame *frame = pfsio.readFrame( stdin ); if( frame == NULL ) { break; } pfs::FrameFile ff = it.getNextFrameFile(); if( ff.fh == NULL ) { pfsio.freeFrame( frame ); break; // No more frames } it.closeFrameFile( ff ); pfs::Channel *X, *Y, *Z, *alpha; frame->getXYZChannels( X, Y, Z ); if( X == NULL ) // No color { Y = frame->getChannel( "Y" ); if( Y == NULL ) // No luminance throw pfs::Exception( "Missing X, Y, Z channels in the PFS stream" ); // Grey levels X = frame->createChannel( "X" ); Z = frame->createChannel( "Z" ); pfs::transformColorSpace( pfs::CS_RGB, Y, Y, Y, pfs::CS_XYZ, X, Y, Z ); } alpha = frame->getChannel( "ALPHA" ); if( alpha == NULL ) alpha = frame->getChannel( "A" ); const char* luminanceTag = frame->getTags()->getString("LUMINANCE"); if( luminanceTag!=NULL && strcmp(luminanceTag,"ABSOLUTE")==0 ) std::cerr << PROG_NAME << " warning: This file format cannot store absolute luminance values\n"; if( opt_srgb ) { if( luminanceTag!=NULL && strcmp(luminanceTag,"DISPLAY")==0 ) std::cerr << PROG_NAME << " warning: This image seems to be display referred thus there is no need for applying the sRGB non-linearity\n"; pfs::transformColorSpace( pfs::CS_XYZ, X, Y, Z, pfs::CS_SRGB, X, Y, Z ); VERBOSE_STR << "writing file (sRGB corrected) '" << ff.fileName << "'" << std::endl; } else { pfs::transformColorSpace( pfs::CS_XYZ, X, Y, Z, pfs::CS_RGB, X, Y, Z ); VERBOSE_STR << "writing file (linear) '" << ff.fileName << "'" << std::endl; } // Determine bit-depth for writing images (bitdepth == -1 for default) int bitdepth = -1; if( opt_bit_depth != -1 ) bitdepth = opt_bit_depth; else { const char* bitDepthTag = frame->getTags()->getString("BITDEPTH"); if( bitDepthTag!=NULL ) { bitdepth=strtol( bitDepthTag, NULL, 10 ); if( bitdepth < 8 ) bitdepth = 8; else if( bitdepth > 16 ) bitdepth = 16; } } { // Copy image to array that can be accepted by ImageMagick const int pixelCount = frame->getWidth()*frame->getHeight(); unsigned short *imgBuffer = new unsigned short[pixelCount*(alpha == NULL ? 3 : 4)]; int i = 0; float maxValue = (float)(1<<16) -1; for( int pix = 0; pix < pixelCount; pix++ ) { imgBuffer[i++] = (unsigned short)(clamp((*X)(pix),0.f,1.f)*maxValue); imgBuffer[i++] = (unsigned short)(clamp((*Y)(pix),0.f,1.f)*maxValue); imgBuffer[i++] = (unsigned short)(clamp((*Z)(pix),0.f,1.f)*maxValue); if( alpha != NULL ) imgBuffer[i++] = (unsigned short)(maxValue-clamp((*alpha)(pix),0.f,1.f)*maxValue); } Magick::Image imImage( frame->getWidth(), frame->getHeight(), (alpha == NULL ? "RGB" : "RGBA"), Magick::ShortPixel, imgBuffer ); imImage.quality( quality ); if( bitdepth != -1 ) imImage.depth( bitdepth ); imImage.write( ff.fileName ); delete[] imgBuffer; } pfsio.freeFrame( frame ); } } int main( int argc, char* argv[] ) { try { writeFrames( argc, argv ); } catch( pfs::Exception ex ) { std::cerr << PROG_NAME << " error: " << ex.getMessage() << std::endl; return EXIT_FAILURE; } catch( Magick::Exception &ex ) { //This is comming from ImageMagick std::cerr << PROG_NAME << " error: " << ex.what() << std::endl; return EXIT_FAILURE; } catch( QuietException ex ) { return EXIT_FAILURE; } return EXIT_SUCCESS; } pfstools-2.2.0/src/fileformat/pfsinexr.cpp0000664000701400070140000002116014105165615017343 0ustar rkm38rkm38/** * @brief Read files in OpenEXR format * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Rafal Mantiuk and Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Grzegorz Krawczyk, * * $Id: pfsinexr.cpp,v 1.4 2013/12/21 19:42:28 rafm Exp $ */ #include #include #include #include #include #include #include #include #include #include #include #include #define PROG_NAME "pfsinexr" using namespace Imf; using namespace Imath; using namespace std; class QuietException { }; void printHelp() { fprintf( stderr, PROG_NAME " [--keep-rgb] [--verbose] [--help]\n" "See man page for more information.\n" ); } static string escapeString( const string &src ) { int pos = 0; string ret = src; while( pos < ret.size() ) { pos = ret.find( "\n", pos ); if( pos == -1 ) break; ret.replace( pos, 1, "\\n" ); pos+=2; } return ret; } void readFrames( int argc, char* argv[] ) { pfs::DOMIO pfsio; bool verbose = false; bool keepRGB = false; // Parse command line parameters static struct option cmdLineOptions[] = { { "help", no_argument, NULL, 'h' }, { "verbose", no_argument, NULL, 'v' }, { "keep-rgb", no_argument, NULL, 'k' }, { "linear", no_argument, NULL, 'l' }, { NULL, 0, NULL, 0 } }; static const char optstring[] = "lkhv"; pfs::FrameFileIterator it( argc, argv, "rb", NULL, NULL, optstring, cmdLineOptions ); int optionIndex = 0; while( 1 ) { int c = getopt_long (argc, argv, optstring, cmdLineOptions, &optionIndex); if( c == -1 ) break; switch( c ) { case 'h': printHelp(); throw QuietException(); case 'v': verbose = true; break; case 'k': keepRGB = true; break; case 'l': std::cerr << PROG_NAME << " warning: linearize option ignored for an HDR input!" << std::endl; break; case '?': throw QuietException(); case ':': throw QuietException(); } } VERBOSE_STR << "keep RGB channels untouch: " << (keepRGB ? "yes" : "no") << std::endl; while( true ) { pfs::FrameFile ff = it.getNextFrameFile(); if( ff.fh == NULL ) break; // No more frames it.closeFrameFile( ff ); InputFile file( ff.fileName ); FrameBuffer frameBuffer; Box2i dw = file.header().displayWindow(); Box2i dtw = file.header().dataWindow(); int width = dw.max.x - dw.min.x + 1; int height = dw.max.y - dw.min.y + 1; if( dtw.min.x < dw.min.x || dtw.max.x > dw.max.x || dtw.min.y < dw.min.y || dtw.max.y > dw.max.y ) throw pfs::Exception( "No support for OpenEXR files DataWidow greater than DisplayWindow" ); pfs::Frame *frame = pfsio.createFrame( width, height ); const ChannelList &channels = file.header().channels(); bool processColorChannels = false; pfs::Channel *X, *Y, *Z; if( !keepRGB ) { // Keep RGB channels as they are const Channel *rChannel = channels.findChannel( "R" ); const Channel *gChannel = channels.findChannel( "G" ); const Channel *bChannel = channels.findChannel( "B" ); if( rChannel!=NULL && gChannel!=NULL && bChannel!=NULL ) { frame->createXYZChannels( X, Y, Z ); frameBuffer.insert( "R", // name Slice( FLOAT, // type // (char*)(X->getRawData()), (char*)(X->getRawData()), sizeof(float), // xStride sizeof(float) * width,// yStride 1, 1, // x/y sampling 0.0)); // fillValue frameBuffer.insert( "G", // name Slice( FLOAT, // type (char*)(Y->getRawData()), sizeof(float), // xStride sizeof(float) * width,// yStride 1, 1, // x/y sampling 0.0)); // fillValue frameBuffer.insert( "B", // name Slice( FLOAT, // type (char*)(Z->getRawData()), sizeof(float), // xStride sizeof(float) * width,// yStride 1, 1, // x/y sampling 0.0)); // fillValue processColorChannels = true; } } for( ChannelList::ConstIterator i = channels.begin(); i != channels.end(); ++i ) { const Channel &channel = i.channel(); if( processColorChannels ) { // Skip color channels if( !strcmp( i.name(), "R" ) || !strcmp( i.name(), "G" ) || !strcmp( i.name(), "B" ) ) continue; } const char *channelName = i.name(); if( !strcmp( channelName, "Z" ) ) { channelName = "DEPTH"; } pfs::Channel *pfsCh = frame->createChannel( channelName ); frameBuffer.insert( i.name(), // name Slice( FLOAT, // type (char *)pfsCh->getRawData(), sizeof(float), // xStride sizeof(float) * width,// yStride 1, 1, // x/y sampling 0.0)); // fillValue } // Copy attributes to tags { for( Header::ConstIterator it = file.header().begin(); it != file.header().end(); it++ ) { const char *attribName = it.name(); const char *colon = strstr( attribName, ":" ); const StringAttribute *attrib = file.header().findTypedAttribute(attribName); if( attrib == NULL ) continue; // Skip if type is not String // fprintf( stderr, "Tag: %s = %s\n", attribName, attrib->value().c_str() ); if( colon == NULL ) { // frame tag frame->getTags()->setString( attribName, escapeString(attrib->value()).c_str() ); } else { // channel tag string channelName = string( attribName, colon-attribName ); pfs::Channel *ch = frame->getChannel( channelName.c_str() ); if( ch == NULL ) { fprintf( stderr, PROG_NAME ": Warning! Can not set tag for '%s' channel because it does not exist\n", channelName.c_str() ); continue; } ch->getTags()->setString( colon+1, escapeString( attrib->value() ).c_str() ); } } } file.setFrameBuffer( frameBuffer ); file.readPixels( dw.min.y, dw.max.y ); VERBOSE_STR << "reading file (linear) '" << ff.fileName << "'" << std::endl; if( processColorChannels ) { // Rescale values if WhiteLuminance is present if( hasWhiteLuminance( file.header() ) ) { float scaleFactor = whiteLuminance( file.header() ); int pixelCount = frame->getHeight()*frame->getWidth(); for( int i = 0; i < pixelCount; i++ ) { (*X)(i) *= scaleFactor; (*Y)(i) *= scaleFactor; (*Z)(i) *= scaleFactor; } // const StringAttribute *relativeLum = // file.header().findTypedAttribute("RELATIVE_LUMINANCE"); const char *luminanceTag = frame->getTags()->getString( "LUMINANCE" ); if( luminanceTag == NULL ) frame->getTags()->setString( "LUMINANCE", "ABSOLUTE" ); } pfs::transformColorSpace( pfs::CS_RGB, X, Y, Z, pfs::CS_XYZ, X, Y, Z ); } frame->getTags()->setString( "FILE_NAME", ff.fileName ); pfsio.writeFrame( frame, stdout ); pfsio.freeFrame( frame ); } } int main( int argc, char* argv[] ) { try { readFrames( argc, argv ); } catch( pfs::Exception ex ) { fprintf( stderr, PROG_NAME " error: %s\n", ex.getMessage() ); return EXIT_FAILURE; } catch (const std::exception &exc) // OpenEXR exception { fprintf( stderr, PROG_NAME " error: %s\n", exc.what() ); return EXIT_FAILURE; } catch( QuietException ex ) { return EXIT_FAILURE; } return EXIT_SUCCESS; } pfstools-2.2.0/src/fileformat/pfsoutffmpeg.10000664000701400070140000000252514105165615017574 0ustar rkm38rkm38.TH "pfsoutffmpeg" 1 .SH NAME pfsoutffmpeg \- Read pfs frames from stdin and forward them to ffmpeg program .SH SYNOPSIS .B pfsoutffmpeg [--help] output_file .SH DESCRIPTION This command is a wrapper for ffmpeg program and can be used to write pfs frames to a compressed video. Check the examples section to for sample usage and see manual of ffmpeg for further information. Note that the usage of '-f' switch (input format specifier) and '-i' switch (input file specifier) of the ffmpeg program is implemented inside the script and is therefore not allowed. .SH EXAMPLES .TP pfsinrgbe frame%04d.hdr --frames 100:2:200 | pfstmo_reinhard02 -s | pfsgamma -g 1.7 | pfsoutffmpeg -qscale 4 test.avi 1. Read HDR frames in RGBE format with the following names: frame0100.hdr, frame0102.hdr, frame0104.hdr,... frame0200.hdr 2. Apply local tone mapping operator to each frame (Reinhard2002 model, part of the PFSTMO package) 3. Apply gamma correction to each tone mapped frame 4. Compress frames to test.avi animation .SH "SEE ALSO" .BR ffmpeg (1) .BR pfsin (1) .BR pfstmo_reinhard02 (1) .BR pfsgamma (1) .SH BUGS Frames smaller than 512x384 can cause broken pipe error, due to bug in ppm pipe reading routine in ffmpeg. Please report bugs and comments to Rafal Mantiuk or Grzegorz Krawczyk . pfstools-2.2.0/src/fileformat/pfsoutexr.cpp0000664000701400070140000003217314105165615017552 0ustar rkm38rkm38/** * @brief Write files in OpenEXR format * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Rafal Mantiuk and Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Grzegorz Krawczyk, * @author Rafal Mantiuk, * * $Id: pfsoutexr.cpp,v 1.4 2013/12/21 19:42:28 rafm Exp $ */ #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Imf; using namespace Imath; #define PROG_NAME "pfsoutexr" class QuietException { }; #define min(x,y) ( (x)<(y) ? (x) : (y) ) void printHelp() { fprintf( stderr, PROG_NAME " [--compression ] [--float32] [--clamp-halfmax] [--linear] [--verbose] [--help]\n" "See man page for more information.\n" ); } bool isColorChannel( pfs::Channel *ch ) { return !strcmp( ch->getName(), "X" ) || !strcmp( ch->getName(), "Y" ) || !strcmp( ch->getName(), "Z" ); } // Change from pfs channel name to OpenEXR channel name const char *exrChannelName( const char *pfsChannelName ) { if( !strcmp( pfsChannelName, "DEPTH" ) ) { return "Z"; } return pfsChannelName; } void writeFrames( int argc, char* argv[] ) { Compression exrCompression = PIZ_COMPRESSION; bool verbose = false; bool keepXYZ = false; bool fixHalfMax = false; bool float32 = false; bool clampHalfMax = false; // Parse command line parameters static struct option cmdLineOptions[] = { { "help", no_argument, NULL, 'h' }, { "verbose", no_argument, NULL, 'v' }, { "compression", required_argument, NULL, 'c' }, { "keep-xyz", no_argument, NULL, 'k' }, { "fix-halfmax", no_argument, NULL, 'f' }, { "clamp-halfmax", no_argument, NULL, 'p' }, { "float32", no_argument, NULL, '3' }, { "linear", no_argument, NULL, 'l' }, { NULL, 0, NULL, 0 } }; static const char optstring[] = "hvc:kf3pl"; pfs::FrameFileIterator it( argc, argv, "wb", NULL, NULL, optstring, cmdLineOptions ); int optionIndex = 0; while( 1 ) { int c = getopt_long (argc, argv, optstring, cmdLineOptions, &optionIndex); if( c == -1 ) break; switch( c ) { case 'h': printHelp(); throw QuietException(); case 'v': verbose = true; break; case 'k': keepXYZ = true; break; case 'f': std::cerr << PROG_NAME << " warning: fix-halfmax is the default behavior starting from 2.0.3. This option is depreciated and will be removed in the future."; break; case '3': float32 = true; break; case 'p': clampHalfMax = true; break; case 'c': if( !strcasecmp( optarg, "NO" ) ) { exrCompression = NO_COMPRESSION; } else if( !strcasecmp( optarg, "RLE" ) ) { exrCompression = RLE_COMPRESSION; } else if( !strcasecmp( optarg, "ZIPS" ) ) { exrCompression = ZIPS_COMPRESSION; } else if( !strcasecmp( optarg, "ZIP" ) ) { exrCompression = ZIP_COMPRESSION; } else if( !strcasecmp( optarg, "PIZ" ) ) { exrCompression = PIZ_COMPRESSION; } else if( !strcasecmp( optarg, "PXR24" ) ) { exrCompression = PXR24_COMPRESSION; } else { throw pfs::Exception( "Unknown compression method. Possible values: NO, RLE, ZIPS, ZIP, PIZ, PXR24" ); } break; case 'l': std::cerr << PROG_NAME << " warning: linearize option ignored for an HDR output!" << std::endl; break; case '?': throw QuietException(); case ':': throw QuietException(); } } // // FrameFileIterator should process only non-option parameters, but // // it assumes that the first parameter is a program name, therefore // // argv+optind-1 // int newArgc = argc-optind+1; if( verbose ) { // && keepXYZ ) //fprintf( stderr, PROG_NAME ": keeping XYZ channels untouched\n" ); fprintf( stderr, PROG_NAME ": Color channel precision: %s\n", float32 ? "32-bit float" : "16-bit float" ); } pfs::DOMIO pfsio; bool firstFrame = true; half *halfRGB[3] = { NULL, NULL, NULL }; //*halfG = NULL, *halfB = NULL; // float *floatRGB[3] = { NULL, NULL, NULL }; //*floatG = NULL, *floatB = NULL; size_t pix_count_prev = 0; while( true ) { pfs::Frame *frame = pfsio.readFrame( stdin ); if( frame == NULL ) { break; // No more frames } const char* luminanceTag = frame->getTags()->getString("LUMINANCE"); if( luminanceTag!=NULL && 0==strcmp(luminanceTag,"DISPLAY") ) { static bool show_once = false; if( !show_once ) { std::cerr << PROG_NAME << " warning: " << "display profiled content written to an HDR output" << std::endl; show_once=true; } } pfs::FrameFile ff = it.getNextFrameFile(); if( ff.fh == NULL ) { pfsio.freeFrame( frame ); break; // No more frames } // OpenEXR library can not use FILE, therefore we do not need FH it.closeFrameFile( ff ); VERBOSE_STR << "writing file (HDR) '" << ff.fileName << "'" << std::endl; // Write the frame to EXR file { // bool colorChannelsToHalf = false; bool storeRGBChannels = false; pfs::Channel *R= NULL, *G = NULL, *B = NULL; // for clarity of the code // if( !keepXYZ ) { pfs::Channel *X, *Y, *Z; frame->getXYZChannels( X, Y, Z ); if( X != NULL ) { // Has color R = X; G = Y; B = Z; pfs::transformColorSpace( pfs::CS_XYZ, X, Y, Z, pfs::CS_RGB, R, G, B ); // colorChannelsToHalf = true; storeRGBChannels = true; } // } Header header( frame->getWidth(), frame->getHeight(), 1, // aspect ratio Imath::V2f (0, 0), // screenWindowCenter 1, // screenWindowWidth INCREASING_Y, // lineOrder exrCompression ); // Define channels in Header { pfs::ChannelIteratorPtr cit( frame->getChannelIterator() ); while( cit->hasNext() ) { pfs::Channel *ch = cit->getNext(); if( storeRGBChannels && isColorChannel( ch ) ) // Skip color channels continue; const char *channelName = exrChannelName( ch->getName() ); header.channels().insert( channelName, Channel(FLOAT) ); } if( storeRGBChannels ) { if( float32 ) { header.channels().insert( "R", Channel(FLOAT) ); header.channels().insert( "G", Channel(FLOAT) ); header.channels().insert( "B", Channel(FLOAT) ); } else { header.channels().insert( "R", Channel(HALF) ); header.channels().insert( "G", Channel(HALF) ); header.channels().insert( "B", Channel(HALF) ); } } } // Copy tags to attributes { pfs::TagIteratorPtr it( frame->getTags()->getIterator() ); while( it->hasNext() ) { const char *tagName = it->getNext(); // fprintf( stderr, "Tag: %s = %s\n", tagName, frame->getTags()->getString( tagName ) ); header.insert( tagName, StringAttribute(frame->getTags()->getString( tagName )) ); } //Copy all channel tags pfs::ChannelIteratorPtr cit( frame->getChannelIterator() ); while( cit->hasNext() ) { pfs::Channel *ch = cit->getNext(); pfs::TagIteratorPtr tit( ch->getTags()->getIterator() ); while( tit->hasNext() ) { const char *tagName = tit->getNext(); std::string channelTagName = ch->getName(); channelTagName += ":"; channelTagName += tagName; header.insert( channelTagName.c_str(), StringAttribute(ch->getTags()->getString( tagName )) ); } } } FrameBuffer frameBuffer; // Create channels in FrameBuffer { pfs::ChannelIterator *it = frame->getChannels(); while( it->hasNext() ) { pfs::Channel *ch = it->getNext(); if( storeRGBChannels && isColorChannel( ch ) ) continue; frameBuffer.insert( exrChannelName( ch->getName() ), // name Slice( FLOAT, // type (char*)ch->getRawData(), // base sizeof(float) * 1, // xStride sizeof(float) * frame->getWidth()) ); // yStride } if( storeRGBChannels ) { static const char *rgb_strings[3] = { "R", "G", "B" }; bool whiteLuminanceUsed = false; if( float32 ) { frameBuffer.insert( rgb_strings[0], // name Slice( FLOAT, // type (char*)R->getRawData(), // base sizeof(float) * 1, // xStride sizeof(float) * frame->getWidth()) ); // yStride frameBuffer.insert( rgb_strings[1], // name Slice( FLOAT, // type (char*)G->getRawData(), // base sizeof(float) * 1, // xStride sizeof(float) * frame->getWidth()) ); // yStride frameBuffer.insert( rgb_strings[2], // name Slice( FLOAT, // type (char*)B->getRawData(), // base sizeof(float) * 1, // xStride sizeof(float) * frame->getWidth()) ); // yStride } else { // Half-float const size_t pix_count = frame->getWidth()*frame->getHeight(); if( pix_count_prev != pix_count ) { // Reallocate memory if needed for( int cc=0; cc<3; cc++ ) { delete [] halfRGB[cc]; halfRGB[cc] = new half[pix_count]; } pix_count_prev = pix_count; } for( int cc=0; cc<3; cc++ ) { frameBuffer.insert( rgb_strings[cc], // name Slice( HALF, // type (char*)halfRGB[cc], // base sizeof(half) * 1, // xStride sizeof(half) * frame->getWidth()) ); // yStride } // Check if pixel values do not exceed maximum HALF value bool maxHalfExceeded = false; float maxValue = -1; if( !clampHalfMax ) { for( int i = 0; i < pix_count; i++ ) { if( (*R)(i) > maxValue ) maxValue = (*R)(i); if( (*G)(i) > maxValue ) maxValue = (*G)(i); if( (*B)(i) > maxValue ) maxValue = (*B)(i); } maxHalfExceeded = maxValue > HALF_MAX; } if( maxHalfExceeded && verbose && !clampHalfMax ) fprintf( stderr, PROG_NAME " warning: Some pixels exceed maximum value that can be stored in an OpenEXR file (maximum value of HALF-16 float). The values are scaled and the \"WhiteLuminance\" tag is added to preserve those values.\n" ); if( maxHalfExceeded ) { // Rescale and copy pixels to half-type buffers float scaleFactor = HALF_MAX/maxValue; for( size_t i = 0; i < pix_count; i++ ) { halfRGB[0][i] = (half)((*R)(i)*scaleFactor); halfRGB[1][i] = (half)((*G)(i)*scaleFactor); halfRGB[2][i] = (half)((*B)(i)*scaleFactor); } // Store scale factor as WhileLuminance standard sttribute // in order to restore absolute values later addWhiteLuminance( header, 1/scaleFactor ); whiteLuminanceUsed = true; } else { // Copy pixels to half-type buffers for( size_t i = 0; i < pix_count; i++ ) { halfRGB[0][i] = min( (*R)(i), HALF_MAX ); halfRGB[1][i] = min( (*G)(i), HALF_MAX ); halfRGB[2][i] = min( (*B)(i), HALF_MAX ); } } } if( luminanceTag != NULL && !strcmp( luminanceTag, "ABSOLUTE" ) && !whiteLuminanceUsed ) { // Use WhiteLuminance tag to signalize absolute values addWhiteLuminance( header, 1 ); } } } OutputFile file(ff.fileName, header); file.setFrameBuffer (frameBuffer); file.writePixels( frame->getHeight() ); } pfsio.freeFrame( frame ); } for( int cc=0; cc<3; cc++ ) { delete [] halfRGB[cc]; } } int main( int argc, char* argv[] ) { try { writeFrames( argc, argv ); } catch( pfs::Exception ex ) { fprintf( stderr, PROG_NAME " error: %s\n", ex.getMessage() ); return EXIT_FAILURE; } catch (const std::exception &exc) // OpenEXR exception { fprintf( stderr, PROG_NAME " error: %s\n", exc.what() ); return EXIT_FAILURE; } catch( QuietException ex ) { return EXIT_FAILURE; } return EXIT_SUCCESS; } pfstools-2.2.0/src/fileformat/pfsinmulti.in0000664000701400070140000000461114105165615017525 0ustar rkm38rkm38#!@BASH_PATH@ ############################################################ # Read several streams of frames and write pfs streams to # named pipes. ############################################################ if test -z "$1" || test "$1" = "--help"; then cat < [...] -- @1 [@2 [..]] See the man page for more information. EOF exit 1 fi all_named_pipes="" do_break="0" while test "$1"; do if test "${1:0:1}" = "-"; then global_arguments="$global_arguments $1" shift continue fi file_pattern="$1" # Get --frames and --skip-frames arguments extra_arguments=""; if test -n "$2"; then if test "$2" = "--"; then shift do_break="1" else while test "${2:0:1}" = "-"; do if test "$2" = "--frames"; then if test -z "$3"; then echo >&2 "Required argument missing after --frames" exit 1; fi extra_arguments="$extra_arguments $2 $3" shift else extra_arguments="$extra_arguments $2" fi shift done fi fi named_pipe=`mktemp` rm -f $named_pipe mkfifo $named_pipe pfsin $global_arguments "$file_pattern" $extra_arguments >$named_pipe & all_named_pipes="$all_named_pipes $named_pipe" shift if test "$do_break" = "1"; then break fi done command=$* new_command=`echo -e "FILES=${all_named_pipes}\nCMD=${command}" | awk '/^FILES/ { for( i=2; i <= NF; i++ ) FILE[i-1]=$i;} /^CMD/ { gsub( "CMD=", "" ); for( i in FILE ) { PAT="@" i; subsc=gsub( PAT, FILE[i] ); if( subsc<1 ) { print( "ERROR" ); exit( 1 );} } print $0;}'` if test "$new_command" = "ERROR"; then echo "pfsinmulti error: You must specify as many @1, @2, .., @n arguments in the command string as there are pfs streams" 1>&2 #remove named pipes cat $all_named_pipes >/dev/null rm -f $all_named_pipes exit 1 fi if ! ${new_command}; then echo "pfsinmulti error: command returned error" 1>&2 # cat $all_named_pipes >/dev/null rm -f $all_named_pipes exit 1 fi rm -f $all_named_pipes pfstools-2.2.0/src/fileformat/pfsout.in0000664000701400070140000001004214105165615016646 0ustar rkm38rkm38#!@BASH_PATH@ ############################################################ # Read pfs frames from stdin and write them in the # format determined by the extension of the file name ############################################################ if test -z "$1" || test "$1" = "--help"; then cat < [...] Recognized file formats and extensions: Radiance RGBE - .pic, .hdr TIFF (incl. LogLuv) - .tiff, .tif PNM, PPM - .ppm, .pnm JPEG - .jpeg, .jpg PNG - .png PFS - .pfs OpenEXR - .exr PFM - .pfm DPX - .dpx GIF - .gif BMP - .bmp EPS - .eps See the man page for more information. EOF exit 1 fi #Arguments used for all images passed to pfsout global_arguments="" if test -n "$1"; then while test "${1:0:1}" = "-"; do #Handle options that require a parameter for par_opt in "--frames" "-f" "--absolute" "-a"; do if test "$1" = $par_opt; then if test -z "$2"; then echo >&2 "Required parameter missing after $par_opt" exit 1; fi global_arguments="$global_arguments $1" shift break; fi done global_arguments="$global_arguments $1" shift done fi while test "$1"; do extension="${1##*.}" file_pattern=$1 # Get --frames and --skip-frames arguments extra_arguments=""; if test -n "$2"; then while test "${2:0:1}" = "-"; do #Handle options that require a parameter for par_opt in "--frames" "-f" "--absolute" "-a"; do if test "$2" = $par_opt; then if test -z "$3"; then echo >&2 "Required parameter missing after $par_opt" exit 1; fi extra_arguments="$extra_arguments $2" shift break; fi done extra_arguments="$extra_arguments $2" shift done fi case "$extension" in ("hdr"|"HDR"|"pic"|"PIC") pfsoutrgbe "$file_pattern" $global_arguments $extra_arguments ;; ("ppm"|"PPM"|"pnm"|"PNM") pfsoutppm "$file_pattern" $global_arguments $extra_arguments ;; ("tif"|"TIF"|"tiff"|"TIFF") if which pfsoutimgmagick >/dev/null; then pfsoutimgmagick "$file_pattern" $global_arguments $extra_arguments else pfsouttiff "$file_pattern" $global_arguments $extra_arguments fi ;; ("exr"|"EXR") pfsoutexr "$file_pattern" $global_arguments $extra_arguments ;; ("pfm"|"PFM") pfsoutpfm "$file_pattern" $global_arguments $extra_arguments ;; ("jpg"|"JPG"|"jpeg"|"JPEG") if which pfsoutimgmagick >/dev/null; then pfsoutimgmagick "$file_pattern" $global_arguments $extra_arguments else pfsoutppm - | pnmtojpeg >$1 fi ;; ("png"|"PNG") if which pfsoutimgmagick >/dev/null; then pfsoutimgmagick "$file_pattern" $global_arguments $extra_arguments else pfsoutppm - | pnmtopng >$1 fi ;; ("dpx"|"DPX"|"gif"|"GIF"|"bmp"|"BMP"|"eps"|"EPS") pfsoutimgmagick "$file_pattern" $global_arguments $extra_arguments ;; ("pfs"|"PFS") cat >$1 ;; ("yuv"|"YUV") pfsoutyuv "$file_pattern" $global_arguments $extra_arguments ;; (*) echo 1>&2 "Unknown extension: $extension" exit 1 ;; esac shift done pfstools-2.2.0/src/fileformat/exrio.h0000664000701400070140000000344214105165615016303 0ustar rkm38rkm38/** * @brief IO operations on OpenEXR file format * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Rafal Mantiuk and Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Grzegorz Krawczyk, * * $Id: exrio.h,v 1.1 2005/06/15 13:36:54 rafm Exp $ */ #ifndef _EXR_IO_H_ #define _EXR_IO_H_ #include #include class OpenEXRReader { Imf::RgbaInputFile* file; /// OpenEXR file object Imath::Box2i dw; /// data window int width, height; public: OpenEXRReader( const char* filename ); ~OpenEXRReader(); int getWidth() const { return width; } int getHeight() const { return height; } void readImage( pfs::Array2D *R, pfs::Array2D *G, pfs::Array2D *B ); }; class OpenEXRWriter { char fileName[1024]; public: OpenEXRWriter( const char* filename ); void writeImage( pfs::Array2D *R, pfs::Array2D *G, pfs::Array2D *B ); }; #endif pfstools-2.2.0/src/fileformat/pfsindcraw.in0000664000701400070140000000544014105165615017474 0ustar rkm38rkm38#!@BASH_PATH@ ############################################################ # Wrapper for libraw and dcraw. # Convert digital camera RAW files to 16bit PPMs. # # this is a stub with basic functionality ############################################################ if test -z "$1" || test "$1" = "--help" || test "$1" = "-h"; then cat < [...] See the man page for more information. EOF exit 1 fi # Use libraw's emulation of dcraw if available libraw="/usr/lib/libraw/dcraw_emu" if which $libraw 2>/dev/null 1>/dev/null; then use_libraw=true elif which dcraw_emu 2>/dev/null 1>/dev/null; then use_libraw=true libraw="dcraw_emu" elif which dcraw 2>/dev/null 1>/dev/null; then use_libraw=false else echo >&2 "pfsindcraw: neither libraw now dcraw program is found. Check if the program is installed and can be found in the PATH." exit 1; fi if ! which pfsinppm 2>/dev/null 1>/dev/null; then echo >&2 "pfsindcraw: pfsinppm program not found. Check if pfstools are compiled with netpbm support." exit 1; fi COLORSPACE=1 # Arguments used for all images passed to pfsindcraw global_arguments="" if test -n "$1"; then while test "${1:0:1}" = "-"; do case "$1" in "--native" | "-n") # Use native (RAW) color space COLORSPACE=0 ;; "--dcraw" | "-d") # Use dcraw instead of libraw use_libraw=false ;; *) echo >&2 "Unrecognized option '$1'." exit 1; esac global_arguments="$global_arguments $1" shift done fi while test "$1"; do # libraw-18 is unable to write to stdout. # Future versions have option "-Z" for this purpose. # TODO: change to -Z option once ubuntu switches to libraw-19 file_pattern="$1" if $use_libraw; then #temp_file=$(mktemp) #cp $file_pattern $temp_file #$libraw -o $COLORSPACE -4 -w $temp_file #pfsinppm ${temp_file}.ppm | \ # pfstag --set "FILE_NAME=${file_pattern}" --set "LUMINANCE=RELATIVE" #rm $temp_file ${temp_file}.ppm # Do not use temp (solves some issues on cygwin) $libraw -o $COLORSPACE -4 -w $file_pattern pfsinppm ${file_pattern}.ppm | \ pfstag --set "FILE_NAME=${file_pattern}" --set "LUMINANCE=RELATIVE" rm $temp_file ${file_pattern}.ppm else dcraw -c -o $COLORSPACE -4 -w "$file_pattern" | pfsinppm - 2> /dev/null | \ pfstag --set "FILE_NAME=${file_pattern}" --set "LUMINANCE=RELATIVE" fi shift done pfstools-2.2.0/src/fileformat/pfsinpfm.10000664000701400070140000000177014105165615016712 0ustar rkm38rkm38.TH "pfsinpfm" 1 .SH NAME pfsinpfm \- Load images or frames in PFM format .SH SYNOPSIS .B pfsinpfm ( [--frames ] [--skip-missing]) [...] .SH DESCRIPTION Use this command to read frames in PFM format. PFM format is used for HDR Shop plug-ins and is also supported by some recent versions of NetPBM package (pamtopfm and pfmtopam commands). The frames are converted to pfs stream and send to the Standard Output. This command can handle both color and gray-scale images. Details on the format of the pattern file names, which are used for reading multiple frames, can be found in the manual page of pfsinppm. To automatically recognize a file format from the extension, use \fIpfsin\fR command instead. .SH EXAMPLES .TP pfsin memorial.pfm | pfsout memorial.hdr Converts from one HDR format to another .SH BUGS Please report bugs and comments on implementation to the discussion group http://groups.google.com/group/pfstools .SH "SEE ALSO" .BR pfsin (1) .BR pfsinppm (1) .BR pfsoutpfm (1) pfstools-2.2.0/src/fileformat/pfsoutrgbe.10000664000701400070140000000613614105165615017251 0ustar rkm38rkm38.TH "pfsoutrgbe" 1 .SH NAME pfsoutrgbe \- Write images or frames in Radiance RGBE format .SH SYNOPSIS .B pfsoutrgbe [--verbose] [--radiance] [--quiet] ( [--frames ]) [...] .SH DESCRIPTION Use this command to the incoming pfs frames (piped to standard input) to Radiance RGBE files (with .hdr extension). .PP Each file name can contain a \%%d pattern, which is substituted with frame numbers. The pattern has the same syntax as C .I printf command. For example, you can use \%%04d to make the frame number four digit with proceedings zeros. You can select the frames using --frames option (see below). .SH OPTIONS .TP .B \--frames The range of frames is given in a format similar to matlab or octave: .B "startframe:step:endframe" Frame numbers start with .B "startframe" (default 0), are increased by .B "step" (default 1) and stop at .B "endframe" You can skip one of those values, for example .I "1:100" for frames 1,2,...,100 and .I 0:2: for frame 0,2,4,... up to the last file that exists. Note that --frames option must be specified after the file name containing \%%d string. higher bit depths. .TP .B \--radiance, -r Correct stored values so that the luminance is reported the same in pfsview and radiance tools (xview or Photospheare). See IMPORTANT NOTE below. .TP .B \--quiet, -q Disable warning message about the change in handling absolute values in RGBE files. See IMPORTANT NOTE below. .SH EXAMPLES .TP pfsin memorial.tiff | pfsoutrgbe memorial.hdr Converts from one HDR format to another .TP pfsin test.jpg | pfsout test.png Convert from one image format to another. .SH "SEE ALSO" .BR pfsout (1) .BR pfsinppm (1) .SH IMPORTANT NOTE There is an incompatibility between viewers of RGBE format in terms of absolute luminance values. The ximage viewer from Radiance package expects radiance values in RGBE files and multiplies the resulting luminance by the WHITE_EFFICACY constant equal to 179.0f to convert from radiance to luminance. .PP To remain compatible with Radiance (xview and Photospheare), pfstools prior to 1.9.0 divided red, green and blue values by 179 on writing and multiplied by that constant on reading RGBE files. This in turn caused incompatibility with other software, such as Photoshop or HDR toolbox. Starting from pfstools 1.9.0 this division / multiplication step was removed to make pfstools compatible in terms of absolute values with the majority of the software. The downside is that this change made .hdr files created with the earlier versions of pfstools incompatible (in terms of absolute luminance units) with the pfstools 1.9.0 or later. To avoid any mistakes because of that change, a long warning messages is shown each time pfsinrgbe or pfsoutrgbe is called. The message can be suppressed with --quiet option. .PP To enable the old functionality and read or write RGBE files with the correction for WHITE_EFFICACY, add --radiance option to the command line. .SH BUGS Please report bugs and comments on implementation to the discussion group http://groups.google.com/group/pfstools pfstools-2.2.0/src/fileformat/pfsinmulti.10000664000701400070140000000311214105165615017252 0ustar rkm38rkm38.TH "pfsinmulti" 1 .SH NAME pfsinmulti \- read several streams of frames and write pfs streams to named pipes .SH SYNOPSIS .B pfsinmulti pfsinmulti [--frames f:s:t] [--skip-frames] [...] -- command @1 @2 [@3 ...] .SH DESCRIPTION Use this command to read several animation sequences and write them to pfsstreams. This command is useful with those pfs programs, which take several pfs streams as arguments. For example, the following command can be used to combine two animations so that there are stitched together: pfsinmulti anim_a-%04d.hdr anim_b-%04d.hdr -- pfscat @1 @2 arguments @1 and @2 are replaced with named pipes for anim_a-%04d.hdr and anim_b-%04d.hdr frames respectively. \fIcommand\fR argument is obligatory and it must be preceded with '--'. There should be as many @1, @2, .., @n arguments as there are animation sequences given as input. Arguments \fB--frames\fR, \fB--skip-frames\fR and other options are handled the same way as in \fIpfsin\fR program. Also pfsinmulti recognizes the same file formats as pfsin. Technically, pfsinmulti creates a named pipe for each pfsstream, replaces @n arguments with the names of those pipes and deletes the pipes when \fIcommand\fR finishes. .SH EXAMPLES .TP pfsinmulti image1.hdr image2.hdr -- cat @1 @2 | pfsview Does the same as 'pfsv image1.hdr image2.hdr' but in much more sophisticated way. .SH "SEE ALSO" .BR pfsin (1) .SH BUGS This command currently does not handle multiple frames given with a \%%d pattern in case of LDR formats: JPEG, PNG, PNM. Please report bugs and comments to Rafal Mantiuk . pfstools-2.2.0/src/fileformat/pfsouttiff.cpp0000664000701400070140000001016314105165615017677 0ustar rkm38rkm38/** * @brief Write files in TIFF format * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Rafal Mantiuk and Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Grzegorz Krawczyk, * * $Id: pfsouttiff.cpp,v 1.3 2008/10/22 22:20:29 rafm Exp $ */ #include #include #include #include #include #include "hdrtiffio.h" #define PROG_NAME "pfsouttiff" class QuietException { }; void printHelp() { std::cerr << PROG_NAME " [--linear] [--verbose] [--help]" << std::endl << "See man page for more information." << std::endl; } void writeFrames( int argc, char* argv[] ) { pfs::DOMIO pfsio; bool verbose = false; bool opt_srgb=false; // Parse command line parameters static struct option cmdLineOptions[] = { { "help", no_argument, NULL, 'h' }, { "verbose", no_argument, NULL, 'v' }, { "srgb", no_argument, NULL, 's' }, { NULL, 0, NULL, 0 } }; static const char optstring[] = "shv"; pfs::FrameFileIterator it( argc, argv, "wb", NULL, stdout, optstring, cmdLineOptions ); int optionIndex = 0; while( 1 ) { int c = getopt_long (argc, argv, optstring, cmdLineOptions, &optionIndex); if( c == -1 ) break; switch( c ) { case 'h': printHelp(); throw QuietException(); case 'v': verbose = true; break; case 's': opt_srgb = true; break; case '?': throw QuietException(); case ':': throw QuietException(); } } while( true ) { pfs::Frame *frame = pfsio.readFrame( stdin ); if( frame == NULL ) { break; } pfs::FrameFile ff = it.getNextFrameFile(); if( ff.fh == NULL ) { pfsio.freeFrame( frame ); break; // No more frames } HDRTiffWriter writer( ff.fh ); pfs::Channel *X, *Y, *Z; frame->getXYZChannels( X, Y, Z ); if( X == NULL ) { // No color throw pfs::Exception( "Missing X, Y, Z channels in the PFS stream" ); } const char* luminanceTag = frame->getTags()->getString("LUMINANCE"); if( luminanceTag!=NULL && strcmp(luminanceTag,"ABSOLUTE")==0 ) std::cerr << PROG_NAME << " warning: This file format cannot store absolute luminance values\n"; if( opt_srgb ) { if( luminanceTag!=NULL && strcmp(luminanceTag,"DISPLAY")==0 ) std::cerr << PROG_NAME << " warning: This image seems to be display referred thus there is no need for applying the sRGB non-linearity\n"; pfs::transformColorSpace( pfs::CS_XYZ, X, Y, Z, pfs::CS_SRGB, X, Y, Z ); VERBOSE_STR << "writing file (sRGB corrected) '" << ff.fileName << "'" << std::endl; } else { pfs::transformColorSpace( pfs::CS_XYZ, X, Y, Z, pfs::CS_RGB, X, Y, Z ); VERBOSE_STR << "writing file (linear) '" << ff.fileName << "'" << std::endl; } writer.writeImage( X, Y, Z ); pfsio.freeFrame( frame ); it.closeFrameFile( ff ); } } int main( int argc, char* argv[] ) { try { writeFrames( argc, argv ); } catch( pfs::Exception ex ) { std::cerr << PROG_NAME << " error: " << ex.getMessage() << std::endl; return EXIT_FAILURE; } catch( QuietException ex ) { return EXIT_FAILURE; } return EXIT_SUCCESS; } pfstools-2.2.0/src/fileformat/pfsoutexr.10000664000701400070140000000557014105165615017131 0ustar rkm38rkm38.TH "pfsoutexr" 1 .SH NAME pfsoutexr \- Write images or frames in OpenEXR format .SH SYNOPSIS .B pfsoutexr [--compression ] [--float32] [--clamp-halfmax] ( [--frames ]) [...] .SH DESCRIPTION Use this command to write frames in OpenEXR format. Source pfs frames should be piped to the Standard Input. This command can write arbitrary channels to OpenEXR as 32-bit floating point numbers. However, color channels are converted to 16-bit half-float format by default unless \fB--float32\fR option is specified. All tags from pfs stream are stored in an OpenEXR file as String attributes. Tags assigned to channels are stored in the format :, so that pfsinexr can later restore tags in the appropriate channels. The depth channel \fIDEPTH\fR is renamed to \fIZ\fR to keep compatibility with \fBpfsinexr\fR. If the luminance is absolute (tag \fILUMINANCE\fR set to \fIABSOLUTE\fR), \fIWhiteLuminance\fR attribute in OpenEXR file is set to 1. Details on the format of the pattern file names, which are used for saving multiple frames, can be found in the manual page of pfsoutppm. .TP .B \--compression , -c Use one the the available compression methods: \fBNO\fR - no compression \fBRLE\fR - run length encoding \fBZIPS\fR - zlib compression, one scan line at a time \fBZIP\fR - zlib compression, in blocks of 16 scan lines \fBPIZ\fR - piz-based wavelet compression (default) \fBPXR24\fR - lossy 24-bit float compression .TP .B \--float32, -3 If this option is specified, color channels are stored as 32-bit floating point numbers instead of 16-bit half-floats. In most cases half-float numbers offer sufficient precision so this the default behavior. Note that storing color channels in 32-bit format makes resulting files very large. .TP .B \--clamp-halfmax, -p The maximum value that can be stored in OpenEXR file is limited to 65504 if 16-bit HALF float is used (color channels are stored by default in this format unless \fB--float32\fR switch is specified). If the luminance data is calibrated in absolute values (cd/m^2), pixel values can easily exceed 65504. To avoid clamping large pixel values, pfsoutexr rescales data to the range that is valid for 16-bit HALF float format and the scale factor used for rescaling is stored in OpenEXR file as OpenEXR standard attribute \fIWhiteLuminance\fR, so that pfsinexr can later restore the absolute values. Use the option \fB--clamp-halfmax\fR to disable this behavior and clamp half-float values instead. .SH EXAMPLES .TP pfsin memorial.hdr | pfsoutexr memorial.exr Converts from one HDR format to another .SH "SEE ALSO" .BR pfsout (1) .BR pfsoutppm (1) .BR pfsinexr (1) .SH BUGS pfsinexr and pfsoutexr can not take stdin / stdout as an input/output (dash '-' instead of file name). Please report bugs and comments on implementation to the discussion group http://groups.google.com/group/pfstools pfstools-2.2.0/src/fileformat/pfsinrgbe.cpp0000664000701400070140000001064314105165615017470 0ustar rkm38rkm38/** * @brief Read files in Radiance's RGBE format * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Rafal Mantiuk and Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Grzegorz Krawczyk, * @author Rafal Mantiuk, * * $Id: pfsinrgbe.cpp,v 1.5 2014/06/17 21:57:08 rafm Exp $ */ #include #include #include #include #include #include "rgbeio.h" #define PROG_NAME "pfsinrgbe" class QuietException { }; void printHelp() { std::cerr << PROG_NAME " [--linear] [--radiance] [--verbose] [--help]" << std::endl << "See man page for more information." << std::endl; } void readFrames( int argc, char* argv[] ) { pfs::DOMIO pfsio; bool verbose = false; bool opt_linear=false; bool radiance_compatibility = false; bool quiet = false; // Parse command line parameters static struct option cmdLineOptions[] = { { "help", no_argument, NULL, 'h' }, { "verbose", no_argument, NULL, 'v' }, { "linear", no_argument, NULL, 'l' }, { "radiance", no_argument, NULL, 'r' }, { "quiet", no_argument, NULL, 'q' }, { NULL, 0, NULL, 0 } }; static const char optstring[] = "lhvrq"; pfs::FrameFileIterator it( argc, argv, "rb", NULL, stdin, optstring, cmdLineOptions ); int optionIndex = 0; while( 1 ) { int c = getopt_long (argc, argv, optstring, cmdLineOptions, &optionIndex); if( c == -1 ) break; switch( c ) { case 'h': printHelp(); throw QuietException(); case 'v': verbose = true; break; case 'r': radiance_compatibility = true; break; case 'q': quiet = true; break; case 'l': std::cerr << PROG_NAME << " warning: linearize option ignored for an HDR input!" << std::endl; break; case '?': throw QuietException(); case ':': throw QuietException(); } } /* if (!quiet && !radiance_compatibility) std::cerr << PROG_NAME << " warning: starting from pfstools 1.9.0, .hdr files are read without correcting for WHITE_EFFICIENCY," " which makes the absolute values incompatible with Radiance and previos versions of pfstools but compatible with most software." " Use --radiance option to retain compatibility with Radiance and earlier versions of pfstools. Use --quiet to suppress this message." " Check the manual pages for pfsinrgbe for details." << std::endl;*/ while( true ) { pfs::FrameFile ff = it.getNextFrameFile(); if( ff.fh == NULL ) break; // No more frames RGBEReader reader( ff.fh, radiance_compatibility ); VERBOSE_STR << "reading file (linear) '" << ff.fileName << "'" << std::endl; pfs::Frame *frame = pfsio.createFrame( reader.getWidth(), reader.getHeight() ); pfs::Channel *X, *Y, *Z; frame->createXYZChannels( X, Y, Z ); //Store RGB data temporarily in XYZ channels reader.readImage( X, Y, Z ); pfs::transformColorSpace( pfs::CS_RGB, X, Y, Z, pfs::CS_XYZ, X, Y, Z ); frame->getTags()->setString("LUMINANCE", "RELATIVE"); const char *fileNameTag = strcmp( "-", ff.fileName )==0 ? "stdin" : ff.fileName; frame->getTags()->setString( "FILE_NAME", fileNameTag ); pfsio.writeFrame( frame, stdout ); pfsio.freeFrame( frame ); it.closeFrameFile( ff ); } } int main( int argc, char* argv[] ) { try { readFrames( argc, argv ); } catch( pfs::Exception ex ) { fprintf( stderr, PROG_NAME " error: %s\n", ex.getMessage() ); return EXIT_FAILURE; } catch( QuietException ) { return EXIT_FAILURE; } return EXIT_SUCCESS; } pfstools-2.2.0/src/fileformat/pfsinimgmagick.cpp0000664000701400070140000001410414105165615020475 0ustar rkm38rkm38/** * @brief Read files using ImageMagics++ library * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Rafal Mantiuk and Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Rafal Mantiuk, * * $Id: pfsinimgmagick.cpp,v 1.6 2014/09/03 08:57:48 rafm Exp $ */ #include #include #include #include #include #define PROG_NAME "pfsinimgmagick" class QuietException { }; void printHelp() { std::cerr << PROG_NAME " [--linear] [--absolute ] [--verbose] [--help]" << std::endl << "See man page for more information." << std::endl; } void readFrames( int argc, char* argv[] ) { pfs::DOMIO pfsio; bool verbose = false; bool opt_linear=false; float absoluteMaxLum = 0; // Parse command line parameters static struct option cmdLineOptions[] = { { "help", no_argument, NULL, 'h' }, { "verbose", no_argument, NULL, 'v' }, { "linear", no_argument, NULL, 'l' }, { "absolute", required_argument, NULL, 'a' }, { NULL, 0, NULL, 0 } }; static const char optstring[] = "lhva:"; pfs::FrameFileIterator it( argc, argv, "rb", NULL, NULL, optstring, cmdLineOptions ); int optionIndex = 0; while( 1 ) { int c = getopt_long (argc, argv, optstring, cmdLineOptions, &optionIndex); if( c == -1 ) break; switch( c ) { case 'h': printHelp(); throw QuietException(); case 'v': verbose = true; break; case 'l': opt_linear = true; break; case 'a': absoluteMaxLum = (float)strtod( optarg, NULL ); break; case '?': throw QuietException(); case ':': throw QuietException(); } } if( absoluteMaxLum != 0 && opt_linear ) throw pfs::Exception( "'absolute' and 'linear' are conflicting options" ); if( absoluteMaxLum < 0 ) throw pfs::Exception( "maximum absolute luminance must be > 0" ); VERBOSE_STR << "linearize input image: " << ((opt_linear || absoluteMaxLum!=0) ? "yes" : "no") << std::endl; if( absoluteMaxLum != 0 ) VERBOSE_STR << "maximum absolute luminance: " << absoluteMaxLum << std::endl; Magick::InitializeMagick(""); while( true ) { pfs::FrameFile ff = it.getNextFrameFile(); if( ff.fh == NULL ) break; // No more frames it.closeFrameFile( ff ); VERBOSE_STR << "reading file '" << ff.fileName << "'" << std::endl; Magick::Image imImage( ff.fileName ); VERBOSE_STR << "input image gamma: " << imImage.gamma() << std::endl; bool hasAlpha = imImage.matte(); if( hasAlpha ) VERBOSE_STR << "alpha channel found" << std::endl; pfs::Frame *frame = pfsio.createFrame( imImage.columns(), imImage.rows() ); pfs::Channel *X, *Y, *Z; frame->createXYZChannels( X, Y, Z ); pfs::Channel *alpha = NULL; if( hasAlpha ) alpha = frame->createChannel( "ALPHA" ); // Copy line by line to pfs::Frame int pixInd = 0; const float maxValue = (float)(1<getWidth()*X->getHeight(); for( int i = 0; i < pixCount; i++ ) { (*X)(i) *= absoluteMaxLum; (*Y)(i) *= absoluteMaxLum; (*Z)(i) *= absoluteMaxLum; } frame->getTags()->setString("LUMINANCE", "ABSOLUTE"); } else frame->getTags()->setString("LUMINANCE", "RELATIVE"); } else { pfs::transformColorSpace( pfs::CS_RGB, X, Y, Z, pfs::CS_XYZ, X, Y, Z ); frame->getTags()->setString("LUMINANCE", "DISPLAY"); } // This is the luminance / luma perceived as reference white // Some tone-mappers may need this frame->getTags()->setString("WHITE_Y", "1"); const char *fileNameTag = strcmp( "-", ff.fileName )==0 ? "stdin" : ff.fileName; frame->getTags()->setString( "FILE_NAME", fileNameTag ); char strbuf[3]; snprintf( strbuf, 3, "%d", (int)imImage.depth() ); frame->getTags()->setString("BITDEPTH", strbuf ); pfsio.writeFrame( frame, stdout ); pfsio.freeFrame( frame ); } } int main( int argc, char* argv[] ) { try { readFrames( argc, argv ); } catch( pfs::Exception ex ) { std::cerr << PROG_NAME << " error: " << ex.getMessage() << std::endl; return EXIT_FAILURE; } catch( Magick::Exception &ex ) { //This is comming from ImageMagick std::cerr << PROG_NAME << " error: " << ex.what() << std::endl; return EXIT_FAILURE; } catch( QuietException ex ) { return EXIT_FAILURE; } return EXIT_SUCCESS; } pfstools-2.2.0/src/fileformat/exrio.cpp0000664000701400070140000000672714105165615016647 0ustar rkm38rkm38/** * @brief IO operations on OpenEXR file format * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Rafal Mantiuk and Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Grzegorz Krawczyk, * * $Id: exrio.cpp,v 1.1 2005/06/15 13:36:54 rafm Exp $ */ #include #include #include #include #include #include "exrio.h" using namespace Imf; using namespace Imath; OpenEXRReader::OpenEXRReader( const char* filename ) { //--- read image file = new RgbaInputFile(filename); dw = file->dataWindow(); width = dw.max.x - dw.min.x + 1; height = dw.max.y - dw.min.y + 1; if( width*height<=0 ) { throw pfs::Exception("EXR: illegal image size"); } DEBUG_STR << "OpenEXR file \"" << filename << "\" (" << width << "x" << height << ")" << endl; } void OpenEXRReader::readImage( pfs::Array2D *R, pfs::Array2D *G, pfs::Array2D *B ) { assert(file!=NULL); DEBUG_STR << "Reading OpenEXR file... " << endl; Imf::Rgba* tmp_img = new Imf::Rgba[width*height]; assert( dw.min.x - dw.min.y * width<=0 ); file->setFrameBuffer(tmp_img - dw.min.x - dw.min.y * width, 1, width); // read image to memory file->readPixels(dw.min.y, dw.max.y); // check if supplied matrixes have the same size as the image if( R->getCols()!=width || R->getRows()!=height || G->getCols()!=width || G->getRows()!=height || B->getCols()!=width || B->getRows()!=height ) { throw pfs::Exception("EXR: matrixes have different size than image"); } int idx=0; for( int y=0 ; ygetCols(); int height = R->getRows(); Imf::Rgba* tmp_img = new Imf::Rgba[width*height]; int idx=0; for( int y=0 ; y [...] .SH DESCRIPTION This command can be used to read high- or low- dynamic range image in several recognized formats and output pfs stream on standard output. The pfs stream is usually piped to another program for further processing (see examples). To get a list of recognized formats and extensions, execute: pfsin --help This command is a front-end for pfsin* programs for reading images: pfsinrgbe, pfsinexr, etc. Based on the file extension, appropriate program is executed. If two different file format are given as parameters, two different program for loading images are executed. Additional options starting with dash '-' can be passed to pfsin* programs. The following rules apply for passing the options: the options given before any image file name (or %d pattern) are passed to all pfsin* programs. Options given after image file name are only passed to the program executed for that file(s). Note also that all option that take an argument (except \fB--frames\fR) must given in the form \fI--option=value\fR, that is without a space between an option and its argument. .SH OPTIONS The following options are shared by most pfsin* commands, although some may not accept --absolute and may ignore --linear. .TP .B \--frames Range is given in mathlab / octave format: .B "startframe:step:endframe" Frame numbers start with .B "startframe" (default 0), are increased by .B "step" (default 1) and stop at .B "endframe" You can skip one of those values, for example .I "1:100" for frames 1,2,...,100 and .I 0:2: for frames 0,2,4,... up to the last file that exists. .TP .B \--skip-missing Skip up to ten frames in a row if corresponding files are missing. Otherwise the program stops reading sequence at the first file that does not exists. This switch does not apply to the first frame in a sequence. This switch can be useful if there is a rendered animation where some of the frame has not been generated. .TP .B \--linear, -l Converts pixel values to linear luminance (XYZ), assuming the sRGB color space for the input image. The maximum pixel value (255,255,255) is mapped to Y=1. \fILUMINANCE\fR tag is set to RELATIVE. .TP .B \--absolute , -a \fB--absolute\fR converts pixel values to an absolute linear luminance (XYZ), that is the color space, in which channel Y contains luminance given in cd/m^2. The sRGB color space is assumed for the input image. The maximum pixel value (255,255,255) is mapped to Y=\fI\fR. \fI\fR is typically set to 80 [cd/m^2] for a CRT monitor. \fILUMINANCE\fR tag is set to ABSOLUTE. \fB--absolute\fR process images almost the same as \fB--relative\fR, but additionally it scales all pixels by \fI\fR. .SH EXAMPLES .TP pfsin memorial.pic | pfsview See a hdr image in Radiance format (RGBE). .TP pfsin memorial.pic | pfstmo_drago03 | pfsout memorial.jpeg Tone map a hdr image and save it as JPEG. .SH "SEE ALSO" .BR pfsout (1) .BR pfsinppm (1) .SH BUGS For LDR formats - JPEG, PNG, PNM: If pfstools are compiled without ImageMagic support, this command currently will not handle multiple frames given with a \%%d pattern. Please report bugs and comments on implementation to the discussion group http://groups.google.com/group/pfstools pfstools-2.2.0/src/fileformat/ppmio.h0000664000701400070140000000351314105165615016300 0ustar rkm38rkm38/** * @brief * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Rafal Mantiuk and Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Rafal Mantiuk, * * $Id: ppmio.h,v 1.5 2009/05/25 19:24:49 rafm Exp $ */ #ifndef PPMIO_H #define PPMIO_H #include #include struct PPMData; class PPMReader { FILE *fh; int width, height; PPMData *data; public: PPMReader( const char *program_name, FILE *fh ); ~PPMReader(); int getWidth() const { return width; } int getHeight() const { return height; } int getBitDepth(); bool readImage( pfs::Array2D *X, pfs::Array2D *Y, pfs::Array2D *Z ); }; class PPMWriter { FILE *fh; const int bit_depth; public: PPMWriter( const char *program_name, FILE *fh, int bit_depth = 8 ); void writeImage( pfs::Array2D *X, pfs::Array2D *Y, pfs::Array2D *Z ); }; #endif pfstools-2.2.0/src/fileformat/pfsoutrgbe.cpp0000664000701400070140000001135414105165615017671 0ustar rkm38rkm38/** * @brief Write files in Radiance's RGBE format * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Rafal Mantiuk and Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Grzegorz Krawczyk, * @author Rafal Mantiuk, * * $Id: pfsoutrgbe.cpp,v 1.3 2014/06/17 21:57:09 rafm Exp $ */ #include #include #include #include #include #include "rgbeio.h" #define PROG_NAME "pfsoutrgbe" class QuietException { }; void printHelp() { std::cerr << PROG_NAME " [--linear] [--verbose] [--radiance] [--quiet] [--help]" << std::endl << "See man page for more information." << std::endl; } void writeFrames( int argc, char* argv[] ) { pfs::DOMIO pfsio; bool verbose = false; bool radiance_compatibility = false; bool quiet = false; // Parse command line parameters static struct option cmdLineOptions[] = { { "help", no_argument, NULL, 'h' }, { "verbose", no_argument, NULL, 'v' }, { "linear", no_argument, NULL, 'l' }, { "radiance", no_argument, NULL, 'r' }, { "quiet", no_argument, NULL, 'q' }, { NULL, 0, NULL, 0 } }; static const char optstring[] = "lhvrq"; pfs::FrameFileIterator it( argc, argv, "wb", NULL, stdout, optstring, cmdLineOptions ); int optionIndex = 0; while( 1 ) { int c = getopt_long (argc, argv, optstring, cmdLineOptions, &optionIndex); if( c == -1 ) break; switch( c ) { case 'h': printHelp(); throw QuietException(); case 'v': verbose = true; break; case 'l': std::cerr << PROG_NAME << " warning: linearize option ignored for an HDR output!" << std::endl; break; case 'r': radiance_compatibility = true; break; case 'q': quiet = true; break; case '?': throw QuietException(); case ':': throw QuietException(); } } /* if (!quiet && !radiance_compatibility) std::cerr << PROG_NAME << " warning: starting from pfstools 1.9.0, .hdr files are written without correcting for WHITE_EFFICIENCY," " which makes the absolute values incompatible with Radiance and previos versions of pfstools but compatible with most software." " Use --radiance option to retain compatibility with Radiance and earlier versions of pfstools. Use --quiet to suppress this message." " Check the manual pages for pfsoutrgbe for details." << std::endl;*/ while( true ) { pfs::Frame *frame = pfsio.readFrame( stdin ); if( frame == NULL ) { break; // No more frames } const char* luminanceTag = frame->getTags()->getString("LUMINANCE"); if( luminanceTag!=NULL && 0==strcmp(luminanceTag,"DISPLAY") ) { static bool show_once = false; if( !show_once ) { std::cerr << PROG_NAME << " warning: " << "display profiled content written to an HDR output" << std::endl; show_once=true; } } pfs::FrameFile ff = it.getNextFrameFile(); if( ff.fh == NULL ) { pfsio.freeFrame( frame ); break; // No more frames } RGBEWriter writer( ff.fh, radiance_compatibility ); pfs::Channel *X, *Y, *Z; frame->getXYZChannels( X, Y, Z ); if( X == NULL ) { // No color throw pfs::Exception( "Missing X, Y, Z channels in the PFS stream" ); } VERBOSE_STR << "writing file (HDR) '" << ff.fileName << "'" << std::endl; pfs::transformColorSpace( pfs::CS_XYZ, X, Y, Z, pfs::CS_RGB, X, Y, Z ); writer.writeImage( X, Y, Z ); pfsio.freeFrame( frame ); it.closeFrameFile( ff ); } } int main( int argc, char* argv[] ) { try { writeFrames( argc, argv ); } catch( pfs::Exception ex ) { std::cerr << PROG_NAME << " error: " << ex.getMessage() << std::endl; return EXIT_FAILURE; } catch( QuietException ex ) { return EXIT_FAILURE; } return EXIT_SUCCESS; } pfstools-2.2.0/src/fileformat/pfsintiff.cpp0000664000701400070140000001056014105165615017477 0ustar rkm38rkm38/** * @brief Read files in TIFF format * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Rafal Mantiuk and Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Grzegorz Krawczyk, * * $Id: pfsintiff.cpp,v 1.3 2008/01/01 13:01:21 rafm Exp $ */ #include #include #include #include #include #include "hdrtiffio.h" #define PROG_NAME "pfsintiff" class QuietException { }; void printHelp() { std::cerr << PROG_NAME " [--linear] [--expmode] [--verbose] [--help]" << std::endl << "See man page for more information." << std::endl; } void readFrames( int argc, char* argv[] ) { pfs::DOMIO pfsio; bool verbose = false; bool opt_linear=false; bool opt_exponential_mode = false; // Parse command line parameters static struct option cmdLineOptions[] = { { "help", no_argument, NULL, 'h' }, { "verbose", no_argument, NULL, 'v' }, { "linear", no_argument, NULL, 'l' }, { "expmode", no_argument, NULL, 'e' }, { NULL, 0, NULL, 0 } }; static const char optstring[] = "elhv"; pfs::FrameFileIterator it( argc, argv, "rb", NULL, stdin, optstring, cmdLineOptions ); int optionIndex = 0; while( 1 ) { int c = getopt_long (argc, argv, optstring, cmdLineOptions, &optionIndex); if( c == -1 ) break; switch( c ) { case 'h': printHelp(); throw QuietException(); case 'v': verbose = true; break; case 'l': opt_linear = true; break; case 'e': opt_exponential_mode = true; break; case '?': throw QuietException(); case ':': throw QuietException(); } } VERBOSE_STR << "linearize input image: " << (opt_linear ? "yes" : "no") << std::endl; VERBOSE_STR << "exponential mode (Lars3 HDR camera): " << (opt_exponential_mode ? "yes" : "no") << std::endl; while( true ) { pfs::FrameFile ff = it.getNextFrameFile(); if( ff.fh == NULL ) break; // No more frames it.closeFrameFile( ff ); HDRTiffReader reader( ff.fileName ); if( opt_exponential_mode ) reader.setExponentialMode(); VERBOSE_STR << "reading file (" << reader.getFormatString() << ") '" << ff.fileName << "'" << std::endl; pfs::Frame *frame = pfsio.createFrame( reader.getWidth(), reader.getHeight() ); pfs::Channel *X, *Y, *Z; frame->createXYZChannels( X, Y, Z ); //Store RGB data temporarily in XYZ channels reader.readImage( X, Y, Z ); if( opt_linear && !reader.isRelative() ) { pfs::transformColorSpace( pfs::CS_SRGB, X, Y, Z, pfs::CS_XYZ, X, Y, Z ); frame->getTags()->setString("LUMINANCE", "RELATIVE"); } else { if( !reader.isColorspaceXYZ() ) pfs::transformColorSpace( pfs::CS_RGB, X, Y, Z, pfs::CS_XYZ, X, Y, Z ); if( reader.isRelative() ) frame->getTags()->setString("LUMINANCE", "RELATIVE"); else frame->getTags()->setString("LUMINANCE", "DISPLAY"); } const char *fileNameTag = strcmp( "-", ff.fileName )==0 ? "stdin" : ff.fileName; frame->getTags()->setString( "FILE_NAME", fileNameTag ); pfsio.writeFrame( frame, stdout ); pfsio.freeFrame( frame ); } } int main( int argc, char* argv[] ) { try { readFrames( argc, argv ); } catch( pfs::Exception ex ) { std::cerr << PROG_NAME << " error: " << ex.getMessage() << std::endl; return EXIT_FAILURE; } catch( QuietException ex ) { return EXIT_FAILURE; } return EXIT_SUCCESS; } pfstools-2.2.0/src/fileformat/pfsinppm.10000664000701400070140000000642514105165615016726 0ustar rkm38rkm38.TH "pfsinppm" 1 .SH NAME pfsinppm \- Load images or frames in PBM formats .SH SYNOPSIS .B pfsinppm ( [--linear] [--absolute ] [--frames ] [--skip-missing]) [...] .SH DESCRIPTION .I pfsinppm command loads images in PBM formats (PPM, PNM or PGM) and writes \fIpfs\fR stream to the standard output. The \fIpfs\fR stream is usually piped to another program for further processing. To detect the format automatically based on the extension, use \fIpfsin\fR command. For more information on PBM formats, refer to the NetPBM web page (http://netpbm.sourceforge.net/). .PP Note that PPM or PNM images are low dynamic range. Therefore pixel values (0-255) are scaled to 0-1 before storing them in pfs stream. Similarly, before writing low dynamic range image from pfs stream, pixel values are multiplied by 255. By default, the 'LUMINANCE' tag is set to 'DISPLAY'. The '--linear' switch can force the inverse sRGB transformation and provide linear data. In this case the 'LUMINANCE' tag is set to 'RELATIVE'. '--absolute' switch can be used to convert pixels to absolute luminance values. .PP To read images from standard input use a single dash '-' instead of filename. The images are read until EOF is reached. .PP Each file can contain a \%%d pattern, which is substituted with frame numbers. The pattern has the same syntax as C .I printf command. For example, you can use \%%04d to make the frame number four digit with proceedings zeros. See the OPTIONS section below for details. .SH OPTIONS .TP .B \--frames Range is given in mathlab / octave format: .B "startframe:step:endframe" Frame numbers start with .B "startframe" (default 0), are increased by .B "step" (default 1) and stop at .B "endframe" You can skip one of those values, for example .I "1:100" for frames 1,2,...,100 and .I 0:2: for frame 0,2,4,... up to the last file that exists. .TP .B \--skip-missing Skip up to ten frames in a row if corresponding files are missing. Otherwise the program stops reading sequence at the first file that does not exists. This switch does not apply to the first frame in a sequence. This switch can be useful if there is a rendered animation where some of the frame has not been generated. .TP .B \--linear, -l Converts pixel values to linear luminance (XYZ), assuming the sRGB color space for the input image. The maximum pixel value (255,255,255) is mapped to Y=1. \fILUMINANCE\fR tag is set to RELATIVE. .TP .B \--absolute , -a \fB--absolute\fR converts pixel values to an absolute linear luminance (XYZ), that is the color space, in which channel Y contains luminance given in cd/m^2. The sRGB color space is assumed for the input image. The maximum pixel value (255,255,255) is mapped to Y=\fI\fR. \fI\fR is typically set to 80 [cd/m^2] for a CRT monitor. \fILUMINANCE\fR tag is set to ABSOLUTE. \fB--absolute\fR process images almost the same as \fB--relative\fR, but additionally it scales all pixels by \fI\fR. .SH EXAMPLES .TP pfsinppm frame\%%04d.ppm \--frames 0:10 | pfsview Read frames from files frame0000.ppm, frame0001.ppm, ..., frame0010.ppm and show them using pfsview. .SH BUGS Please report bugs and comments on implementation to the discussion group http://groups.google.com/group/pfstools .SH "SEE ALSO" .BR pfsin (1), .BR pfsout (1) pfstools-2.2.0/src/fileformat/pfsinyuv.cpp0000664000701400070140000005262114105165615017376 0ustar rkm38rkm38/** * @brief Read RAW Yuv files, commonly used for video compression * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2017 Rafal Mantiuk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author George Ash * */ #include #include #include #include #include #include //#include #include #include #include #include #include #define PROG_NAME "pfsinyuv" char charsToRemove[7] = {'b', 'i', 't', 's', 'f', 'p', 's'}; //PL means a planar format per frame. enum ChromaFormat {PL420, PL444, V210}; template inline T clamp( T val, T min, T max ) { if( val < min ) return min; if( val > max ) return max; return val; } template bool read_yuv_channel( FILE *fh, int width, int height, int stride, pfs::Array2D *dest, float gain, float offset, float min, float max ) { T *line_buf = new T[width]; for( int y = 0; y < height; y++ ) { size_t read = fread( line_buf, sizeof( T ), width, fh ); if( y == 0 && read == 0 ) { // End of file reached delete [] line_buf; return false; } if( read != width ) { delete [] line_buf; throw pfs::Exception( "Error when reading Yuv file" ); } for( int x = 0; x < width; x++ ) { float v_float = (float)line_buf[x]*gain - offset; (*dest)(x<getRows(); const int width = ch->getCols(); // For even rows for( int y = 0; y < height; y += 2 ) { float v_l = (*ch)(0,y); float v_r; int x; for( x = 1; x < (width-1); x += 2 ) { v_r = (*ch)(x+1,y); // Interpolate between pixels on the left and right (*ch)(x,y) = (v_l + v_r)/2.f; v_l = v_r; } if( x < width ) (*ch)(x,y) = v_r; } // For odd rows int y; for( y = 1; y < (height-1); y += 2 ) { for( int x = 0; x < width; x++ ) { (*ch)(x,y) = ((*ch)(x,y-1) + (*ch)(x,y+1))/2.f; } } // Last row if( y < height ) { for( int x = 0; x < width; x++ ) { (*ch)(x,y) = (*ch)(x,y-1); } } } void upscale_422to444( pfs::Array2D *ch ) { const int height = ch->getRows(); const int width = ch->getCols(); // For odd columns for (int y = 0; y < height; y++){ for( int x = 1; x < width-1; x+=2 ) { (*ch)(x,y) = ((*ch)(x-1,y) + (*ch)(x+1,y))/2.f; } } } void parse210Block(unsigned int word, pfs::Array2D *thirdPart, pfs::Array2D *secondPart, pfs::Array2D *firstPart, unsigned int thirdPartX, unsigned int secondPartx, unsigned int firstPartX, unsigned int y, bool ycy){ //last bool denotes the ordering of luma and chroma within a word. not ycy -? cyc const unsigned int bitmask = 0x03FF; const float luma_offset = 16.f/219.f; const float luma_gain = 1.f/( (float)(1<<(2))*219.f); const float chroma_offset = 128.f/224.f; const float chroma_gain = 1.f/( (float)(1<<(2))*224.f); //now because of the somewhat arbitrary ordering, we don't know beforehand what offset and gain to apply. I'm just trying to make this less messy, but there's definitely a better way. TODO this. float first_last_gain = ycy ? luma_gain : chroma_gain; float first_last_offset = ycy ? luma_offset : chroma_offset; float second_gain = ycy ? chroma_gain : luma_gain; float second_offset = ycy ? chroma_offset : luma_offset; (*thirdPart)(thirdPartX,y) = (float)((word&(bitmask<<20))>>20)*first_last_gain - first_last_offset; (*secondPart)(secondPartx,y) = (float)((word&(bitmask<<10))>>10)*second_gain - second_offset; (*firstPart)(firstPartX,y) = (float)(word&(bitmask))*first_last_gain - first_last_offset; } bool read_v210_yuv( FILE *fh, int width, int height, pfs::Array2D *Ydest,pfs::Array2D *Cbdest, pfs::Array2D *Crdest){ assert(sizeof(unsigned int) == 4); //2*width needed because width is needed to store luma, and width is also needed to store both chroma unsigned int paddedLineWidth = (unsigned int) ceil(((float)width)/6.)*4; //in 32 bit words unsigned int * line_buf = new unsigned int[paddedLineWidth]; for( int y = 0; y split(std::string s, std::string delim){ return std::vector(); } bool parseFileName(const char * fileNameIn, int * width, int * height, int * bitdepth, pfs::ColorSpace * colorspace, ChromaFormat * chromaFormat, int * fps){ try{ /* Parses the filename, puts the result into fields pointed to by the arguments returns true if all went well, false if there's a malformed filename string should be _x____ Regex would be nice*/ std::string fileName(fileNameIn); const std::string delimiter = "_"; size_t pos = 0; std::string token; std::vector tokens; while ((pos = fileName.find(delimiter)) != std::string::npos) { token = fileName.substr(0, pos); tokens.push_back(token); fileName.erase(0, pos + delimiter.length()); } tokens.push_back(fileName); for (std::vector::iterator tok = tokens.begin() + 1; tok != tokens.end(); ++tok) { if (contains(*tok, "x")){ sscanf(tok->c_str(), "%dx%d", width, height); } else if(*tok == "10" || *tok == "8" || *tok == "12" || tok->at(tok->length() - 1) == 'b' || contains(*tok, "bit")){ //last character is a b *bitdepth = parseIntField(*tok); } else if(*tok == "24" || *tok == "25" || *tok == "50" || *tok == "60" || contains(*tok, "fps")){ *fps = parseIntField(*tok); } else if (contains(*tok, "pq") || (contains(*tok, "2020") && !contains(*tok, "hlg"))){ *colorspace = pfs::CS_PQYCbCr2020; } else if (contains(*tok, "bt") || contains(*tok, "709")){ *colorspace = pfs::CS_YCbCr709; } else if (contains(*tok, "hlg")){ *colorspace = pfs::CS_HLGYCbCr2020; } else if(contains(*tok, "444")) *chromaFormat = PL444; else if(contains(*tok, "420")) *chromaFormat = PL420; else if(contains(*tok, "V210")) *chromaFormat = V210; } return true; } catch(...){ return false; } } class YUVReader { FILE *fh; int width, height; int bit_depth; ChromaFormat chromaFormat; unsigned long int fileSize; enum ColorSpace { REC709, REC2020 }; ColorSpace color_space; public: YUVReader( FILE *fh, int width, int height, int bit_depth, ChromaFormat chromaFormat) : fh(fh), width( width ), height( height ), bit_depth( bit_depth ), chromaFormat( chromaFormat ) { //initialize filesize fseek(fh, 0L, SEEK_END); fileSize = ftell(fh); //go back from where we came fseek(fh, 0L, SEEK_SET); } int getWidth() const { return width; } int getHeight() const { return height; } unsigned long int getFileSize() const { return fileSize; } /* Advances a number of frames through the file without reading them */ // bool advanceFrames(int frameNo){ // int divisor = subsampling_420 ? 4 : 1; //this is the integer divisor of the green and blue channels (they are 4 times smaller if 4:2:0 subsampling is enabled) // long int offset = frameNo * (/*Red:*/ width * height * sizeof(STORE_FORMAT) + /*Green, Blue:*/ 2*width*height*sizeof(STORE_FORMAT)/divisor); // return fseek(fh, offset, SEEK_CUR); // } /* Seek to a given frame */ bool seekToFrame(int frameNo){ unsigned long int offset = getFileOffsetFromFrame(frameNo); if(offset > fileSize){ throw pfs::Exception("Seeking past EOF, is your given frame range within the range of the input?"); } else{ return fseek(fh, offset, SEEK_SET); } } //gets the position in the file of the specified frame unsigned long int getFileOffsetFromFrame(int frameNo){ if(chromaFormat == V210){ /*TODO This*/ return (unsigned long int) ceil(((float) width)/6)*16*height*frameNo; } else{ unsigned long int divisor = chromaFormat == PL420 ? 4 : 1; //this is the integer divisor of the green and blue channels (they are 4 times smaller if 4:2:0 subsampling is enabled) if( frameNo <= 0 ) throw pfs::Exception( "Invalid frame index. Frame are indexed from 1" ); unsigned long int storeFormatSize = bit_depth <= 8 ? sizeof(unsigned char) : sizeof(unsigned short); unsigned long int offset = (frameNo-1) * (/*Luma:*/ width * height * storeFormatSize + /*CrCb:*/ 2*width*height*storeFormatSize/divisor); return offset; } } /** * Read a single frame from Yuv video file and store in 3 RGB channels of type pfs::Array. * Integer values are converted into floating point values. * * @return TRUE if the frame has been loaded successfully, FALSE if end-of-file. * Exception is thrown if there is an issue reading a full frame. */ bool readImage( pfs::Array2D *R, pfs::Array2D *G, pfs::Array2D *B ){ if(chromaFormat == V210){ return read_v210_yuv(fh, width, height, R, G, B); } else{ if(bit_depth <= 8){ return readImageTyped(R, G, B); } else{ return readImageTyped(R, G, B); } } } private: template bool readImageTyped( pfs::Array2D *R, pfs::Array2D *G, pfs::Array2D *B) { { // Read Y const float offset = 16.f/219.f; const float gain = 1.f/( (float)(1<<(bit_depth-8))*219.f); if( !read_yuv_channel( fh, width, height, 0, R, gain, offset, 0.f, 1.f ) ) { return false; } } { // Read Cb, Cr const float offset = 128.f/224.f; const float gain = 1.f/( (float)(1<<(bit_depth-8))*224.f); unsigned int chroma_width = width; unsigned int chroma_height = height; unsigned int chroma_stride = 0; if (chromaFormat == PL420){ chroma_width = width/2; chroma_height = height/2; chroma_stride = 1; } if( !read_yuv_channel( fh, chroma_width, chroma_height, chroma_stride, G, gain, offset, -0.5f, 0.5f ) ) throw pfs::Exception( "EOF reached when reading the Cb portion of a frame" ); if( !read_yuv_channel( fh, chroma_width, chroma_height, chroma_stride, B, gain, offset, -0.5f, 0.5f ) ) throw pfs::Exception( "EOF reached when reading the Cr portion of a frame" ); if(chromaFormat == PL420){ upscale_420to444( B ); upscale_420to444( G ); } } return true; } }; class QuietException { }; void printHelp() { std::cerr << PROG_NAME " [--verbose] [--quiet] [--width] [--height] [--colorspace] [--no-guess] [--chroma-format] [--bit-depth] [--frames] [--help]" << std::endl << "See man page for more information." << std::endl; } void readFrames( int argc, char* argv[] ) { pfs::DOMIO pfsio; bool verbose = false; bool quiet = false; bool opt_noguess = false; int opt_width = -1; int opt_height = -1; int opt_bitdepth = -1; const char * opt_chroma_subsampling = NULL; int opt_frame_min = -1; int opt_frame_max = -1; int opt_frame_stride = 0; int opt_fps = -1; char * filename; pfs::ColorSpace opt_colorspace = pfs::CS_INVALID; // Parse command line parameters static struct option cmdLineOptions[] = { { "help", no_argument, NULL, 'p' }, { "verbose", no_argument, NULL, 'v' }, { "width", required_argument, NULL, 'w' }, { "height", required_argument, NULL, 'h' }, { "bit-depth", required_argument, NULL, 'b' }, { "colorspace", required_argument, NULL, 'c' }, { "quiet", no_argument, NULL, 'q' }, { "no-guess", no_argument, NULL, 'n'}, { "fps", required_argument, NULL, 'f'}, { "chroma-format", required_argument, NULL, 's'}, { "frames", required_argument, NULL, 'r'}, { NULL, 0, NULL, 0 } }; static const char optstring[] = "-pw:h:vqb:c:f:nr:s:"; int optionIndex = 0; while( 1 ) { int c = getopt_long (argc, argv, optstring, cmdLineOptions, &optionIndex); if(c == -1){ break; } switch( c ) { case 'p': printHelp(); throw QuietException(); case 'v': verbose = true; break; case 'w': opt_width = strtol( optarg, NULL, 10 ); break; case 'h': opt_height = strtol( optarg, NULL, 10 ); break; case 'b': opt_bitdepth = strtol( optarg, NULL, 10 ); break; case 's': opt_chroma_subsampling = optarg; break; case 'n': opt_noguess = true; break; case 'f': opt_fps = strtol(optarg, NULL, 10); break; case 'r': { pfs::parseFrameRange(optarg, opt_frame_min, opt_frame_max, opt_frame_stride); VERBOSE_STR << "Reading frames " << opt_frame_min << ":" << opt_frame_stride << ":" << opt_frame_max << std::endl; break; } case 'c': if( !strcasecmp( optarg, "pq2020" ) ) { opt_colorspace = pfs::CS_PQYCbCr2020; } else if( !strcasecmp( optarg, "bt709" )) { opt_colorspace = pfs::CS_YCbCr709; } else if( !strcasecmp( optarg, "hlg2020")){ opt_colorspace = pfs::CS_HLGYCbCr2020; } else { throw pfs::Exception( "Unrecognized colorspace name" ); } break; case 'q': quiet = true; break; case '?': throw QuietException(); case ':': throw QuietException(); case '\1': filename = optarg; } } FILE * fh; if(filename != NULL){ fh = fopen (filename, "rb"); } if( fh == NULL ) { throw pfs::Exception("Couldn't find a filename to open, did you provide one"); }; // No more frames int width = -1, height = -1, bitdepth = -1, frame_min = 1, frame_max = INT_MAX, frame_stride = 1, fps = -1; ChromaFormat chromaFormat = PL444; pfs::ColorSpace colorspace = pfs::CS_INVALID; VERBOSE_STR << "reading file '" << filename << "'" << std::endl; //we infer the metadata from the filename unless noguess is specified if(!opt_noguess){ std::string rawName(filename); //these three lines extract the filename proper, without the containing directory prefix long unsigned int substring = rawName.find_last_of("\\/"); rawName = rawName.substr(substring == std::string::npos ? 0 : substring); bool goodFileName = parseFileName(rawName.c_str(), &width, &height, &bitdepth, &colorspace, &chromaFormat, &fps); if(!goodFileName){ VERBOSE_STR << "Unable to parse filename: " << filename << "\n"; } } // Over-ride the auto-recognized params with the ones specified as arguments if( opt_width > -1 ) width = opt_width; if( opt_height > -1 ) height = opt_height; if( opt_bitdepth > -1 ) bitdepth = opt_bitdepth; if( opt_frame_min > -1 ) frame_min = opt_frame_min; if( opt_frame_max > -1 ) frame_max = opt_frame_max; if( opt_frame_stride != 0) frame_stride = opt_frame_stride; if( opt_colorspace != pfs::CS_INVALID ) colorspace = opt_colorspace; if( opt_chroma_subsampling != NULL ){ //set up our chromat supsampling, defaults to planar 444 chromaFormat = strcmp(opt_chroma_subsampling, "420") == 0 ? PL420 : strcmp(opt_chroma_subsampling, "V210") == 0 ? V210 : PL444; } if( opt_fps > 0 ){ fps = opt_fps; } VERBOSE_STR << "Yuv file " << width << "x" << height << " " << bitdepth << "bits" << std::endl; switch( colorspace ) { case pfs::CS_PQYCbCr2020: VERBOSE_STR << "colorspace: HDR PQ BT2020" << std::endl; break; case pfs::CS_YCbCr709: VERBOSE_STR << "colorspace: SDR BT709" << std::endl; break; case pfs::CS_HLGYCbCr2020: VERBOSE_STR << "colorspace: HDR HLG BT2020" << std::endl; break; } if( width <= 0 || height <= 0 ) throw pfs::Exception( "Unspecified or incorrect resolution of the Yuv file" ); if( bitdepth < 8 || bitdepth > 16 ) throw pfs::Exception( "Unspecified or incorrect bit-depth of the Yuv file" ); if( frame_min < 0 || frame_max < 0){ throw pfs::Exception( "Frame range is invalid "); } if( colorspace == pfs::CS_INVALID ){ throw pfs::Exception( "Unspecified colorspace of the Yuv file" ); } if( fps > 0 ){ VERBOSE_STR << "FPS: " << fps << std::endl; } YUVReader reader( fh, width, height, bitdepth, chromaFormat); int currentFrame = frame_min; while( (currentFrame <= frame_max && frame_stride > 0) || (currentFrame >= frame_max && frame_stride < 0) ) { //inclusive pfs::Frame *frame = pfsio.createFrame( reader.getWidth(), reader.getHeight() ); pfs::Channel *X, *Y, *Z; frame->createXYZChannels( X, Y, Z ); if(reader.seekToFrame(currentFrame)) { //some error occured in reading :( pfsio.freeFrame( frame ); break; } //Store RGB data temporarily in XYZ channels bool success = reader.readImage( X, Y, Z ); if( !success ) { // EOF reached pfsio.freeFrame( frame ); break; } VERBOSE_STR << "Reading frame " << currentFrame << std::endl; if( colorspace == pfs::CS_YCbCr709 ) { // The trick to get LDR data in the pfs stream pfs::transformColorSpace( colorspace, X, Y, Z, pfs::CS_SRGB, X, Y, Z ); pfs::transformColorSpace( pfs::CS_RGB, X, Y, Z, pfs::CS_XYZ, X, Y, Z ); } else { pfs::transformColorSpace( colorspace, X, Y, Z, pfs::CS_XYZ, X, Y, Z ); } switch( colorspace ) { case pfs::CS_PQYCbCr2020: frame->getTags()->setString("LUMINANCE", "ABSOLUTE"); break; case pfs::CS_YCbCr709: frame->getTags()->setString("LUMINANCE", "DISPLAY"); break; case pfs::CS_HLGYCbCr2020: frame->getTags()->setString("LUMINANCE", "ABSOLUTE"); break; } if( fps > 0 ){ frame->getTags()->setString("FPS", pfs::intToString(fps).c_str()); } const char *fileNameTag = strcmp( "-", filename )==0 ? "stdin" : filename; frame->getTags()->setString( "FILE_NAME", fileNameTag ); pfsio.writeFrame( frame, stdout ); pfsio.freeFrame( frame ); currentFrame += frame_stride; } fclose(fh); } int main( int argc, char* argv[] ) { try { readFrames( argc, argv ); } catch( pfs::Exception ex ) { fprintf( stderr, PROG_NAME " error: %s\n", ex.getMessage() ); return EXIT_FAILURE; } catch( QuietException ) { return EXIT_FAILURE; } return EXIT_SUCCESS; } pfstools-2.2.0/src/fileformat/pfsoutyuv.cpp0000664000701400070140000003335114105165615017576 0ustar rkm38rkm38 /** * @brief Write files in Radiance's RGBE format * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Rafal Mantiuk and Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Grzegorz Krawczyk, * @author Rafal Mantiuk, * * $Id: pfsoutrgbe.cpp,v 1.3 2014/06/17 21:57:09 rafm Exp $ */ #include #include #include #include #include #include #include #include #define PROG_NAME "pfsoutyuv" template T clip3(T x, T y, T z){ if(zy){ return y; } return z; } class MetaDataFrameFileIterator : pfs::FrameFileIteratorImpl { unsigned int width; unsigned int height; unsigned int bitdepth; unsigned int fps; const char * colorSpace; const char * chromaFormat; //Used for YUV output - we'll override this from FrameFileIterator - because we only want one YUV file writing to - and the rest of the class is useful public: MetaDataFrameFileIterator( int &argc, char* argv[], const char *fopenMode, const char *fileNamePrefix, FILE *stdinout, const char *optstring, const struct option *getopt_long, unsigned int width, unsigned int height, unsigned int bitdepth, const char * colorSpace, const char * chromaFormat, int fps) : pfs::FrameFileIteratorImpl(argc, argv, fopenMode, fileNamePrefix, stdinout, optstring, getopt_long), bitdepth(bitdepth), colorSpace(colorSpace), width(width), height(height), chromaFormat(chromaFormat), fps(fps) { } pfs::FrameFile getNextFrameFile(){ if(!currentPattern->isPattern){ strcpy(fileName, currentPattern->pattern); } else{ char metaData[100]; //reasonably safe? if(fps>0){ sprintf(metaData, "%dx%d_%dfps_%db_%s_%s", width, height, fps, bitdepth, colorSpace, chromaFormat); } else{ sprintf(metaData, "%dx%d_%db_%s_%s", width, height, bitdepth, colorSpace, chromaFormat); } sprintf(fileName,currentPattern->pattern, metaData); } //grab prefix FILE *fh; if( currentPattern->stdinout != NULL ) fh = currentPattern->stdinout; else fh = fopen( fileName, fopenMode ); if( fh != NULL ){ return pfs::FrameFile( fh, fileName ); } else{ throw pfs::Exception("Cound't open file for saving"); } return pfs::FrameFile(NULL, NULL); } void closeFrameFile(pfs::FrameFile ff){ pfs::FrameFileIteratorImpl::closeFrameFile(ff); } }; class FilterableArray2D : public pfs::Array2D { Array2D * base; public: FilterableArray2D(pfs::Array2D * in){ base = in; } int getCols() const{ return base->getCols(); } int getRows() const{ return base->getRows(); } float& operator()( int col, int row ){ if(col < 0 || col >= base->getCols() || row < 0 || row >= base->getRows()){ return *(new float(0)); } else{ return (*base)(col,row); } } const float& operator()( int col, int row ) const{ if(col < 0 || col >= base->getCols() || row < 0 || row >= base->getRows()){ return *(new float(0)); } else{ return (*base)(col,row); } } float& operator()( int index ){ return (*base)(index); } const float& operator()( int index ) const{ return (*base)(index); } }; class QuietException { }; enum DownsampleFilter{f0, f1}; void printHelp() { std::cerr << PROG_NAME " [--verbose] [--quiet] [--bitdepth] [--colorspace] [--downsample-filter] [--chroma-format]" << std::endl << "See man page for more information." << std::endl; } void downscale_444_to_420(pfs::Array2D * channel, DownsampleFilter method){ float weightsF0[] = {0.125, 0.75, 0.125}; //weights for f0 filter method float weightsF1[] = {0.25, 0.5, 0.25}; //ditto for f1 float * weights; switch(method){ case f0 : weights = weightsF0; case f1 : weights = weightsF1; } //downscales a single channel //do rows first FilterableArray2D chan(channel); for (int i = 0; i void writeYuvChannel(pfs::Array2D * in, int width, int height, int stride, float scale, float offset){ for( int j = 0; j void writeYuvImage(pfs::Array2D *R, pfs::Array2D *G, pfs::Array2D *B){ //writes the entire image to fh R - Luminance, G - Cr, B - Cb unsigned int width_chroma = R->getCols(); unsigned int height_chroma = R->getRows(); unsigned int step_chroma = 0; if(downscale_chroma){ width_chroma = R->getCols()/2; height_chroma = R->getRows()/2; step_chroma = 1; downscale_444_to_420(G, downsampleFilter); downscale_444_to_420(B, downsampleFilter); } writeYuvChannel(R, R->getCols(), R->getRows(), 0, 219.0f*(1<<(bitdepth-8)), 1<<(bitdepth -4)); writeYuvChannel(G, width_chroma, height_chroma, step_chroma, 224.0f*(1<<(bitdepth-8)), 1<<(bitdepth -1)); writeYuvChannel(B, width_chroma, height_chroma, step_chroma, 224.0f*(1<<(bitdepth-8)), 1<<(bitdepth -1)); } }; void writeFrames( int argc, char* argv[] ) { pfs::DOMIO pfsio; bool verbose = false; int bit_depth = 10; unsigned int width; unsigned int height; unsigned int fps = 0; bool quiet = false; bool downscale_chroma = true; pfs::ColorSpace opt_colorspace = pfs::CS_INVALID; DownsampleFilter downsampleFilter = f0; bool srgb_input = false; // Input is in sRGB colour space if true, RGB otherwise bool opt_srgb_input = false; bool opt_linear_input = false; // Parse command line parameters static struct option cmdLineOptions[] = { { "help", no_argument, NULL, 'h' }, { "verbose", no_argument, NULL, 'v' }, { "bitdepth", required_argument, NULL, 'b' }, { "colourspace", required_argument, NULL, 'c' }, { "quiet", no_argument, NULL, 'q' }, { "downsample-filter", required_argument, NULL, 'a'}, { "chroma-format", required_argument, NULL, 'f'}, { "srgb-input", no_argument, NULL, 's'}, { "linear-input", no_argument, NULL, 'l'}, { NULL, 0, NULL, 0 } }; static const char optstring[] = "hb:vqc:f:a:sl"; int optionIndex = 0; while( 1 ) { int c = getopt_long (argc, argv, optstring, cmdLineOptions, &optionIndex); if( c == -1 ) break; switch( c ) { case 'h': printHelp(); throw QuietException(); case 'v': verbose = true; break; case 'b': bit_depth = strtol( optarg, NULL, 10 ); break; case 'a': if( strcmp( optarg, "f0") == 0){ downsampleFilter = f0;} else if( strcmp( optarg, "f1") == 0){ downsampleFilter = f1;} break; case 'f': if( strcmp( optarg, "444") == 0){ downscale_chroma = false; } else if ( strcasecmp( optarg, "420") == 0){ downscale_chroma = true; } else{ throw pfs::Exception( "Unrecognised chroma-sampling format" ); } break; case 'c': if( !strcasecmp(optarg, "pq2020" )) { opt_colorspace = pfs::CS_PQYCbCr2020; } else if( !strcasecmp(optarg, "bt709" )) { opt_colorspace = pfs::CS_YCbCr709; } else if( !strcasecmp(optarg, "hlg2020" )) { opt_colorspace = pfs::CS_HLGYCbCr2020; } else{ throw pfs::Exception( "Unrecognized colorspace name" ); } break; case 'q': quiet = true; break; case 's': opt_srgb_input = true; break; case 'l': opt_linear_input = true; break; case '?': throw QuietException(); case ':': throw QuietException(); } } //we need to open this here to grab metadata for the filename :) pfs::Frame *frame = pfsio.readFrame( stdin ); pfs::Channel *fullDimensionChannel; fullDimensionChannel = frame->getChannel("X"); if( fullDimensionChannel == NULL ) { // No color throw pfs::Exception( "Missing X channel in the PFS stream" ); } width = fullDimensionChannel->getCols(); height = fullDimensionChannel->getRows(); const char* luminanceTag = frame->getTags()->getString("LUMINANCE"); const char* fpsTag = frame->getTags()->getString("FPS"); if( opt_colorspace == pfs::CS_INVALID ) { // Try to guess the right colour space if( luminanceTag!=NULL && 0==strcmp(luminanceTag,"DISPLAY") ) { opt_colorspace = pfs::CS_YCbCr709; } else if( luminanceTag!=NULL && 0==strcmp(luminanceTag,"ABSOLUTE") ) { opt_colorspace = pfs::CS_PQYCbCr2020; } else throw pfs::Exception( "Color space must be specified" ); } const char * colorSpaceStr; switch( opt_colorspace ) { case pfs::CS_YCbCr709: colorSpaceStr = "bt709"; break; case pfs::CS_PQYCbCr2020: colorSpaceStr = "pq2020"; break; case pfs::CS_HLGYCbCr2020: colorSpaceStr = "hlg2020"; break; default: throw pfs::Exception( "Unrecognized color space" ); } if(fpsTag != NULL){ VERBOSE_STR << "FPS Tag: " << fpsTag << std::endl; fps = atoi(fpsTag); } if( opt_srgb_input ) { srgb_input = true; } else if( opt_linear_input ) { srgb_input = false; } else { if( opt_colorspace == pfs::CS_YCbCr709 ) srgb_input = true; } if( !srgb_input && luminanceTag!=NULL && 0==strcmp(luminanceTag,"DISPLAY") ) { std::cerr << "pfsoutyuv: Warning - assuming linear (HDR) input while the stream may be gamma-corrected (LDR)." << std::endl; } if( srgb_input && luminanceTag!=NULL && 0==strcmp(luminanceTag,"ABSOLUTE") ) { std::cerr << "pfsoutyuv: Warning - assuming gamma-corrected (LDR) input while the stream may be linear (HDR)." << std::endl; } if( luminanceTag!=NULL && 0==strcmp(luminanceTag,"RELATIVE") ) { std::cerr << "pfsoutyuv: Warning - relative HDR pixel values detected. Absolute values should be provided." << std::endl; } if( srgb_input ) VERBOSE_STR << "Input data in sRGB (LDR) space" << std::endl; else VERBOSE_STR << "Input data in absolute linear (HDR) space" << std::endl; MetaDataFrameFileIterator it(argc, argv, "wb", NULL, stdout, optstring, cmdLineOptions, width, height, bit_depth, colorSpaceStr, downscale_chroma ? "420" : "444", fps); pfs::FrameFile ff = it.getNextFrameFile(); if( ff.fh == NULL ) { throw pfs::Exception("Couldn't open file for writing"); } else { VERBOSE_STR << "Saving file: " << ff.fileName << std::endl << width << "x" << height << ", Bit-depth: " << bit_depth << ", Colorspace: " << getColorspaceString(opt_colorspace) << ", " << (downscale_chroma ? "420, " : "444, ") << std::endl; } while(true){ YUVWriter writer( ff.fh, bit_depth, downscale_chroma, downsampleFilter); pfs::Channel *X, *Y, *Z; frame->getXYZChannels( X, Y, Z ); if( X == NULL || Y == NULL || Z == NULL) { // No color throw pfs::Exception( "Missing X, Y, Z channels in the PFS stream" ); } if( srgb_input ) { pfs::transformColorSpace( pfs::CS_XYZ, X, Y, Z, pfs::CS_RGB, X, Y, Z ); pfs::transformColorSpace( pfs::CS_SRGB, X, Y, Z, opt_colorspace, X, Y, Z ); } else{ pfs::transformColorSpace( pfs::CS_XYZ, X, Y, Z, opt_colorspace, X, Y, Z ); } if( bit_depth <= 8){ writer.writeYuvImage( X, Y, Z ); } else{ writer.writeYuvImage( X, Y, Z ); } pfsio.freeFrame( frame ); frame = pfsio.readFrame( stdin ); if( frame == NULL ) { break; // No more frames } } it.closeFrameFile( ff ); } int main( int argc, char* argv[] ) { try { writeFrames( argc, argv ); } catch( pfs::Exception ex ) { std::cerr << PROG_NAME << " error: " << ex.getMessage() << std::endl; return EXIT_FAILURE; } catch( QuietException ex ) { return EXIT_FAILURE; } return EXIT_SUCCESS; } pfstools-2.2.0/src/fileformat/pfsindcraw.10000664000701400070140000000326714105165615017233 0ustar rkm38rkm38.TH "pfsindcraw" 1 .SH NAME pfsindcraw \- Read an image in a camera RAW file format supported by DCRAW or LIBRAW and write pfs stream to the standard output .SH SYNOPSIS .B pfsindcraw [--\fBnative\fR] [...] .SH DESCRIPTION This command can be used to read images in a camera RAW file format supported by DCRAW or LIBRAW and write pfs stream to the standard output as if read from 16bit ppm file (no gamma correction, white balance from camera if available). In principle such data should conform to the definition of High Dynamic Range contents used in the pfstools package. .PP A raw image may in some cases require clamping to remove zero values, see examples. .SH OPTIONS .TP --\fBnative\fR, -\fBn\fR Read images in native camera color space (instead of sRGB). To be used with pfscolortransform. Must be specified before any file name. --\fBdcraw\fR, -\fBd\fR Read images using the original dcraw tool even if libraw is available. .SH EXAMPLES .TP pfsindcraw img_0070.cr2 | pfsview View a full dynamic range of a raw image from Canon 350D camera. .TP pfsindcraw img_0070.cr2 | pfsclamp --rgb | pfstmo_drago03 | pfsout img.jpg Tone map a raw image and save it as JPEG. Clamping is used to remove zero values, which otherwise result in NaN values after tone mapping. .TP pfsindcraw --native img_0070.cr2 | pfscolortransform --rgbxyz canon500d.txt | pfsout img.exr Load a RAW image in its native camera color space, calibrate using matrix stored in canon500d.txt file (not supplied) and save as an EXR image. .SH "SEE ALSO" .BR pfsout (1) .BR pfsinppm (1) .BR pfscolortransform (1) .SH BUGS Please report bugs and comments to the pfstools discussion group (http://groups.google.com/group/pfstools). pfstools-2.2.0/src/fileformat/rgbeio.cpp0000664000701400070140000002556014105165615016764 0ustar rkm38rkm38/** * @brief IO operations on Radiance's RGBE file format * * This file is a part of PFSTOOLS package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Rafal Mantiuk and Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Grzegorz Krawczyk, * * $Id: rgbeio.cpp,v 1.13 2014/06/17 21:57:09 rafm Exp $ */ #include "config.h" #include #include #include #include #include "rgbeio.h" using namespace std; /// constant to change between radiance and luminance #define WHITE_EFFICACY 179.0f void readRadianceHeader( FILE *file, int &width, int &height, float &exposure ); void readRadiance( FILE *file, int width, int height, float exposure, pfs::Array2D *X, pfs::Array2D *Y, pfs::Array2D *Z ); void writeRadiance(FILE *file, pfs::Array2D *X, pfs::Array2D *Y, pfs::Array2D *Z, bool radiance_compatibility = false ); //--- // RGBE IO classes implementation RGBEReader::RGBEReader(FILE *fh, bool radiance_compatibility) : fh(fh), radiance_compatibility(radiance_compatibility) { //TODO: read header from radiance file readRadianceHeader( fh, width, height, exposure ); } void RGBEReader::readImage( pfs::Array2D *X, pfs::Array2D *Y, pfs::Array2D *Z ) { if (radiance_compatibility) readRadiance(fh, width, height, exposure / WHITE_EFFICACY, X, Y, Z); else readRadiance(fh, width, height, exposure, X, Y, Z); } RGBEReader::~RGBEReader() { //TODO: empty } void RGBEWriter::writeImage( pfs::Array2D *X, pfs::Array2D *Y, pfs::Array2D *Z ) { writeRadiance(fh, X, Y, Z, radiance_compatibility); } //-------------------------------------------------------------------- // RGBE IO format support functions typedef unsigned char Trgbe; struct Trgbe_pixel { /// @name RGB values and their exponent //@{ Trgbe r; Trgbe g; Trgbe b; Trgbe e; //@} }; void rgbe2rgb(const Trgbe_pixel& rgbe, float exposure, float &r, float &g, float &b) { if( rgbe.e!=0 ) // a non-zero pixel { int e = rgbe.e - int(128+8); // double f = ldexp( 1.0, e ) * WHITE_EFFICACY / exposure; double f = ldexp(1.0, e) / exposure; r = (float)(rgbe.r * f); g = (float)(rgbe.g * f); b = (float)(rgbe.b * f); } else r = g = b = 0.f; } void rgb2rgbe( float r, float g, float b, Trgbe_pixel& rgbe) { // r /= WHITE_EFFICACY; // g /= WHITE_EFFICACY; // b /= WHITE_EFFICACY; double v = r; // max rgb value if( v < g) v = g; if( v < b ) v = b; if( v < 1e-32 ) { rgbe.r = rgbe.g = rgbe.b = rgbe.e = 0; } else { int e; // exponent v = frexp(v,&e) * 256.0/v; rgbe.r = Trgbe( v*r ); rgbe.g = Trgbe( v*g ); rgbe.b = Trgbe( v*b ); rgbe.e = Trgbe( e+128 ); } } //-------------------------------------------------------------------- // RGBE IO support functions // Reading RGBE files void readRadianceHeader( FILE *file, int &width, int &height, float &exposure ) { DEBUG_STR << "RGBE: reading header..." << endl; // read header information char head[255]; float fval; int format=0; exposure = 1.0f; while( !feof(file) ) { fgets(head, 200, file); if( strcmp(head, "\n")==0 ) break; if( strcmp(head, "#?RADIANCE\n")==0 ) { // format specifier found format=1; } if( strcmp(head, "#?RGBE\n")==0 ) { // format specifier found format=1; } if( head[0]=='#' ) // comment found - skip continue; if( strcmp(head, "FORMAT=32-bit_rle_rgbe\n")==0 ) { // header found continue; } if( sscanf(head, "EXPOSURE=%f", &fval)==1 ) { // // exposure value // if( exposure==0.0f ) // exposure = fval; // else exposure *= fval; } } // // if exposure is not set, set it to WHITE_EFFICACY // if( exposure==0.0f ) // exposure = WHITE_EFFICACY; // ignore wierd exposure adjustments if( exposure>1e12 || exposure<1e-12 ) exposure=1.0f; if( !format ) { throw pfs::Exception( "RGBE: no format specifier found" ); } // image size char xbuf[4], ybuf[4]; if( fgets(head,sizeof(head)/sizeof(head[0]),file) == 0 || sscanf(head,"%3s %d %3s %d",ybuf,&height,xbuf,&width) != 4 ) { throw pfs::Exception( "RGBE: unknown image size" ); } /* if( ybuf[1]=='x' || ybuf[1]=='X' ) { height += width; width = height - width; height = height - width; } */ DEBUG_STR << "RGBE: image size " << width << "x" << height << endl; } void RLERead( FILE* file, Trgbe* scanline, int size ) { int peek=0; while( peek128 ) { // a run int run_len = p[0]-128; while( run_len>0 ) { scanline[peek++] = p[1]; run_len--; } } else { // a non-run scanline[peek++] = p[1]; int nonrun_len = p[0]-1; if( nonrun_len>0 ) { fread(scanline+peek, sizeof(*scanline), nonrun_len, file); peek += nonrun_len; } } } if( peek!=size ) { throw pfs::Exception( "RGBE: difference in size while reading RLE scanline"); } } void readRadiance( FILE *file, int width, int height, float exposure, pfs::Array2D *X, pfs::Array2D *Y, pfs::Array2D *Z ) { // read image // depending on format read either rle or normal (note: only rle supported) Trgbe* scanline = new Trgbe[width*4]; for( int y=0 ; y4 ) { // write a non run: scanline[0] to scanline[run_start] if( run_start>0 ) { Trgbe* buf = new Trgbe[run_start+1]; buf[0] = run_start; for( int i=0 ; igetCols(); int height = X->getRows(); DEBUG_STR << "RGBE: writing image " << width << "x" << height << endl; if( Y->getCols() != width || Y->getRows() != height || Z->getCols() != width || Z->getRows() != height ) { throw pfs::Exception( "RGBE: RGB layers have different size"); } // header information fprintf(file, "#?RADIANCE\n"); // file format specifier fprintf(file, "# PFStools writer to Radiance RGBE format\n"); // if( exposure_isset ) // fprintf(file, "EXPOSURE=%f\n", exposure); // if( gamma_isset ) // fprintf(file, "GAMMA=%f\n", gamma); fprintf(file, "FORMAT=32-bit_rle_rgbe\n"); // if (exposure != 1.f) // fprintf(file, "EXPOSURE=%f\n", exposure); fprintf(file, "\n"); // image size fprintf(file, "-Y %d +X %d\n", height, width); // image run length encoded Trgbe* scanlineR = new Trgbe[width]; Trgbe* scanlineG = new Trgbe[width]; Trgbe* scanlineB = new Trgbe[width]; Trgbe* scanlineE = new Trgbe[width]; for( int y=0 ; y> 8;; header[3] = width & 0xFF; fwrite(header, sizeof(header), 1, file); // each channel is encoded separately for( int x=0 ; x] [--mul ] [--local] [--cone ] [--rod ] [--verbose] [--help] .SH DESCRIPTION This command implements a tone mapping operator as described in: Time-Dependent Visual Adaptation for Realistic Image Display. S.N. Pattanaik, J. Tumblin, H. Yee, and D.P. Greenberg. In Proceedings of ACM SIGGRAPH 2000. This operator requires properly calibrated image data (in cd/m2) and its results should be gamma corrected. The local version of this operator is based on the following paper: Adaptive Gain Control For High Dynamic Range Image Display. S.N. Pattanaik, H. Yee. In proceedings of SCCG 2002. .SH OPTIONS .TP --time-dependence, -t Use time dependent model of adaptation. This should be used for tone mapping of an animation. It has no influence on single-frame input. Switched off by default. Note: it is important to correctly set the speed of the input animation (--fps). .TP --fps , -f Speed of input animation in frames per second. Used when time-dependence is switched on (-t). Default value: 16.0 .TP --mul , -m Multiply input values by a multiplier value. Useful if input data is not calibrated. Default value: 1.0 .TP --local, -l Use local version of the tone mapping. Time-dependent effects are cancelled while using local version, global version is used by default. .TP --cone , -c Set the adaptation level for cones. If --rod parameter is not used, this will also set the adaptation level for rods. By default, the adaptation level is calculated as a logarithmic average of luminance in the input image. .TP --rod , -r Set the adaptation level for rods. By default, the adaptation level is calculated as a logarithmic average of luminance in the input image. .TP --verbose Print additional information during program execution. .TP --help Print list of command line options. .SH EXAMPLES .TP pfsin memorial.hdr | pfstmo_pattanaik00 -m 650 | pfsgamma -g 2.2 | pfsout memorial.png Tone map image, apply gamma correction, and save it in png format. .SH "SEE ALSO" .BR pfsgamma (1) .BR pfsin (1) .BR pfsout (1) .BR pfsview (1) .SH BUGS Please report bugs and comments on implementation to Grzegorz Krawczyk . pfstools-2.2.0/src/tmo/pattanaik00/CMakeLists.txt0000664000701400070140000000074114105165615020326 0ustar rkm38rkm38include_directories ("${PROJECT_BINARY_DIR}/" "${PROJECT_SOURCE_DIR}/src/pfs" "${PROJECT_SOURCE_DIR}/src/tmo/pfstmo") if( NOT HAS_GETOPT ) include_directories ("${GETOPT_INCLUDE}") endif( NOT HAS_GETOPT ) link_directories("${PROJECT_SOURCE_DIR}/src/pfs") set(TRG pfstmo_pattanaik00) add_executable(${TRG} ${TRG}.cpp tmo_pattanaik00.cpp "${GETOPT_OBJECT}") target_link_libraries(${TRG} pfs) install (TARGETS ${TRG} DESTINATION bin) install (FILES ${TRG}.1 DESTINATION ${MAN_DIR}) pfstools-2.2.0/src/tmo/pattanaik00/pfstmo_pattanaik00.cpp0000664000701400070140000001502514105165615021777 0ustar rkm38rkm38/** * @file pfstmo_pattanaik00.cpp * @brief Tone map XYZ channels using Pattanaik00 model * * Time-Dependent Visual Adaptation for Realistic Image Display * S.N. Pattanaik, J. Tumblin, H. Yee, and D.P. Greenberg * In Proceedings of ACM SIGGRAPH 2000 * * * This file is a part of PFSTMO package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Grzegorz Krawczyk, * * $Id: pfstmo_pattanaik00.cpp,v 1.3 2008/09/04 12:46:49 julians37 Exp $ */ #include #include #include #include #include #include #include #include "tmo_pattanaik00.h" using namespace std; #define PROG_NAME "pfstmo_pattanaik00" class QuietException { }; void multiplyChannels( pfs::Array2D* X, pfs::Array2D* Y, pfs::Array2D* Z, float mult ); void printHelp() { fprintf( stderr, PROG_NAME " (" PACKAGE_STRING ") : \n" "\t[--time-dependence] [--local] \n" "\t[--mul ] \n" "\t[--cone ] [--rod ] \n" "\t[--verbose] [--help]\n" "See man page for more information.\n" ); } void pfstmo_pattanaik00( int argc, char* argv[] ) { pfs::DOMIO pfsio; //--- default tone mapping parameters; bool timedependence = false; bool local = false; float multiplier = 1.0f; float Acone = -1.0f; float Arod = -1.0f; float fps = 16.0f; //--- process command line args bool verbose = false; static struct option cmdLineOptions[] = { { "help", no_argument, NULL, 'h' }, { "verbose", no_argument, NULL, 'v' }, { "time-dependence", no_argument, NULL, 't' }, { "fps", required_argument, NULL, 'f' }, { "local", no_argument, NULL, 'l' }, { "mul", required_argument, NULL, 'm' }, { "cone", required_argument, NULL, 'c' }, { "rod", required_argument, NULL, 'r' }, { NULL, 0, NULL, 0 } }; int optionIndex = 0; while( 1 ) { int c = getopt_long (argc, argv, "hvtf:lm:c:r:", cmdLineOptions, &optionIndex); if( c == -1 ) break; switch( c ) { case 'h': printHelp(); throw QuietException(); case 'v': verbose = true; break; case 't': timedependence = true; break; case 'l': local = true; break; case 'f': fps = (float)strtod( optarg, NULL ); if( fps<=0.0f ) throw pfs::Exception("incorrect frames per second value, should be non-zero positive"); break; case 'm': multiplier = (float)strtod( optarg, NULL ); if( multiplier<=0.0f ) throw pfs::Exception("incorrect multiplier value, should be non-zero positive"); break; case 'c': Acone = (float)strtod( optarg, NULL ); if( Acone<=0.0f ) throw pfs::Exception("incorrect cone adaptation value, should be non-zero positive"); if( Arod==-1.0f ) Arod=Acone; break; case 'r': Arod = (float)strtod( optarg, NULL ); if( Arod<=0.0f ) throw pfs::Exception("incorrect rod adaptation value, should be non-zero positive"); break; case '?': throw QuietException(); case ':': throw QuietException(); } } VERBOSE_STR << "time-dependence: " << (timedependence ? "yes" : "no") << endl; if( timedependence ) VERBOSE_STR << "frames per sec.: " << fps << endl; VERBOSE_STR << "local: " << (local ? "yes" : "no") << endl; VERBOSE_STR << "multiplier: " << multiplier << endl; VisualAdaptationModel* am = new VisualAdaptationModel(); bool firstFrame = true; while( true ) { pfs::Frame *frame = pfsio.readFrame( stdin ); if( frame == NULL ) break; // No more frames pfs::Channel *X, *Y, *Z; frame->getXYZChannels( X, Y, Z ); frame->getTags()->setString("LUMINANCE", "RELATIVE"); //--- if( Y==NULL || X==NULL || Z==NULL) throw pfs::Exception( "Missing X, Y, Z channels in the PFS stream" ); // adaptation model if( multiplier!=1.0f ) multiplyChannels( X, Y, Z, multiplier ); if( !local ) { if( firstFrame || !timedependence ) { if( Acone!=-1.0f ) am->setAdaptation(Acone,Arod); else am->setAdaptation(Y); firstFrame = false; } else am->calculateAdaptation(Y, 1.0f/fps); VERBOSE_STR << "adaptation cone: " << am->getAcone() << endl; VERBOSE_STR << "adaptation rod: " << am->getArod() << endl; VERBOSE_STR << "bleaching cone: " << am->getBcone() << endl; VERBOSE_STR << "bleaching rod: " << am->getBrod() << endl; } // tone mapping int w = Y->getCols(); int h = Y->getRows(); pfs::Array2DImpl* R = new pfs::Array2DImpl(w,h); pfs::Array2DImpl* G = new pfs::Array2DImpl(w,h); pfs::Array2DImpl* B = new pfs::Array2DImpl(w,h); pfs::transformColorSpace( pfs::CS_XYZ, X, Y, Z, pfs::CS_RGB, R, G, B ); tmo_pattanaik00( w, h, R->getRawData(), G->getRawData(), B->getRawData(), Y->getRawData(), am, local ); pfs::transformColorSpace( pfs::CS_RGB, R, G, B, pfs::CS_XYZ, X, Y, Z ); delete R; delete G; delete B; //--- pfsio.writeFrame( frame, stdout ); pfsio.freeFrame( frame ); } delete am; // delete visual adaptation model } void multiplyChannels( pfs::Array2D* X, pfs::Array2D* Y, pfs::Array2D* Z, float mult ) { int size = Y->getCols() * Y->getRows(); for( int i=0 ; i * * $Id: tmo_pattanaik00.h,v 1.2 2008/09/04 12:46:49 julians37 Exp $ */ #ifndef _tmo_pattanaik00_h_ #define _tmo_pattanaik00_h_ #include class VisualAdaptationModel; /** * @brief: Tone mapping algorithm [Pattanaik2000] * * @param width image width * @param height image height * @param R red channel * @param G green channel * @param B blue channel * @param Y luminance channel * @param am pointer to adaptation model * @param local false: use global version, true: use local version */ void tmo_pattanaik00( unsigned int width, unsigned int height, float* R, float* G, float* B, const float* Y, VisualAdaptationModel* am, bool local=false ); /** * @brief Time-dependent Visual Adaptation Model * * Class for storing state of visual adaptation model. */ class VisualAdaptationModel { private: /// cone's adaptation level float Acone; /// rod's adaptation level float Arod; /// cone's bleaching term float Bcone; /// rod's bleaching term float Brod; /// calculate logarithmic average of Y float calculateLogAvgLuminance( pfs::Array2D* Y ); public: /** * @brief Constructor * Sets default adaptation state (office light conditions) */ VisualAdaptationModel(); /** * @brief Calculate adaptation level according to Visual Adaptation Model * * @param Gcone goal adaptation value for cones * @param Grod goal adaptation value for rods * @param dt time [s] that passed after last calculation of adaptation */ void calculateAdaptation(float Gcone, float Grod, float dt); /** * @brief Calculate adaptation level according to Visual Adaptation Model * * @param Y luminance map of HDR image * @param dt time [s] that passed after last calculation of adaptation */ void calculateAdaptation(pfs::Array2D *Y, float dt); /** * @brief Set adaptation level to given values * * @param Gcone goal adaptation value for cones * @param Grod goal adaptation value for rods */ void setAdaptation(float Gcone, float Grod); /** * @brief Set adaptation level appropriate for given lumiance map * * @param Y luminance map of HDR image */ void setAdaptation(pfs::Array2D *Y); /// Get cone adaptation level float getAcone() { return Acone; }; /// Get rod adaptation level float getArod() { return Arod; }; /// Get cone bleaching term float getBcone() { return Bcone; }; /// Get rod bleaching term float getBrod() { return Brod; }; }; #endif /* _tmo_pattanaik00_h_ */ pfstools-2.2.0/src/tmo/pattanaik00/tmo_pattanaik00.cpp0000664000701400070140000002445014105165615021270 0ustar rkm38rkm38/** * @file tmo_pattanaik00.cpp * @brief Time-dependent Visual Adaptation Model, [Pattanaik2000] * * Time-Dependent Visual Adaptation for Realistic Image Display * S.N. Pattanaik, J. Tumblin, H. Yee, and D.P. Greenberg * In Proceedings of ACM SIGGRAPH 2000 * * * This file is a part of PFSTMO package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Grzegorz Krawczyk, * * $Id: tmo_pattanaik00.cpp,v 1.4 2009/04/15 11:49:32 julians37 Exp $ */ #include #include #include "tmo_pattanaik00.h" #include "pfstmo.h" /// sensitivity of human visual system float n = 0.73f; void calculateLocalAdaptation(const pfstmo::Array2D* Y, int x, int y, float& Acone, float& Arod); float sigma_response_rod(float I); float sigma_response_cone(float I); float model_response(float I, float sigma); // tone mapping operator code void tmo_pattanaik00( unsigned int width, unsigned int height, float* nR, float* nG, float* nB, const float* nY, VisualAdaptationModel* am, bool local ) { ///--- initialization of parameters const pfstmo::Array2D* Y = new pfstmo::Array2D(width, height, const_cast(nY)); pfstmo::Array2D* R = new pfstmo::Array2D(width, height, nR); pfstmo::Array2D* G = new pfstmo::Array2D(width, height, nG); pfstmo::Array2D* B = new pfstmo::Array2D(width, height, nB); /// cones level of adaptation float Acone = am->getAcone(); /// rods level of adaptation float Arod = am->getArod(); /// cones bleaching term float Bcone = am->getBcone(); /// rods bleaching term float Brod = am->getBrod(); /// fraction of adaptation luminance that represents white luminance float white_factor = 5.0f; /// fraction of adaptation luminance that represents dark luminance float dark_factor = 32.0f/5.0f; /// goal adaptation luminance while watching display float G_display=25.0f; /// display luminance of white float display_white = G_display*white_factor; /// display luminance of dark float display_dark = G_display/dark_factor; /// half-saturation constant for display float display_sigma = sigma_response_cone(G_display); /// reference white for display float REF_Wht_display = model_response(display_white, display_sigma); /// reference black for display float REF_Blk_display = model_response(display_dark, display_sigma); /// display color saturation float S_d = (REF_Wht_display - REF_Blk_display) / (log10(display_white)-log10(display_dark)); ///--- precalculated parameters /// half-saturation constant for cones float sigma_cone = sigma_response_cone(Acone); /// half-saturation constant for rods float sigma_rod = sigma_response_rod(Arod); // appearance model float scene_white_cone = Acone*white_factor; float scene_dark_cone = Acone/dark_factor; float scene_white_rod = Arod*white_factor; float scene_dark_rod = Arod/dark_factor; float REF_Wht_scene = Bcone*model_response(scene_white_cone, sigma_cone) + Brod*model_response(scene_white_rod, sigma_rod); float REF_Blk_scene = Bcone*model_response(scene_dark_cone, sigma_cone) + Brod*model_response(scene_dark_rod, sigma_rod); /// scene luminance minus shift float disp_x = 0.0f; /// scene luminance to display luminance scale factor float disp_y = 1.0f; /// display luminance plus shift float disp_z = 0.0f; float scale = (REF_Wht_display-REF_Blk_display) / (REF_Wht_scene-REF_Blk_scene); if( scale < 1.0f ) { if( REF_Wht_scene>REF_Wht_display ) { disp_x = REF_Wht_scene; disp_y = scale; disp_z = REF_Wht_display; } else if( REF_Blk_scene 1.0f ) { if( REF_Wht_scene>REF_Wht_display ) { disp_x = REF_Wht_scene; disp_y = 1.0f; disp_z = REF_Wht_display; } else if( REF_Blk_scenegetCols(); int im_height = Y->getRows(); for( int y=0 ; y0.0f ) { Rrod /= Rlum; Rcone /= Rlum; } float Scolor = (Bcone*pow(sigma_cone,n)*n*pow(l,n)) / pow( pow(l,n)+pow(sigma_cone,n), 2 ); Scolor /= S_d; // appearance model float Ra = (Rlum - disp_x)*disp_y+disp_z; Ra = (Ra<1.0f) ? ((Ra>0.0f) ? Ra : 0.0f ) : 0.9999999f; // inverse display model float I = display_sigma * pow(Ra/(1.0f-Ra), 1.0f/n) / display_white; // apply new luminance r = pow( r, Scolor )*I*Rcone + I*Rrod; g = pow( g, Scolor )*I*Rcone + I*Rrod; b = pow( b, Scolor )*I*Rcone + I*Rrod; (*R)(x,y) = (r<1.0f) ? ((r>0.0f) ? r : 0.0f) : 1.0f; (*G)(x,y) = (g<1.0f) ? ((g>0.0f) ? g : 0.0f) : 1.0f; (*B)(x,y) = (b<1.0f) ? ((b>0.0f) ? b : 0.0f) : 1.0f; } } delete B; delete G; delete R; delete Y; } /////////////////////////////////////////////////////////// float sigma_response_rod(float I) { //!! INFO: this is essentially the same as below (formulas come from // R.W.G.Hunt book) // float j = 0.00001f / (5.0f*I + 0.00001f); // float fls = 3800*pow(j,2.0f)*5*I+0.2*pow(1-pow(j,2.0f),4.0f)*pow(5*I,1.0f/6.0f); // float sigma_rod = pow(2,1.0f/n) / fls * I; float j = 1.0f / (5*1e4*I+1); float j2 = j*j; float sigma_rod = (2.5874f*I)/(19000.0f*j2*I+0.2615f*pow(1.0f-j2,4)*pow(I,1.0f/6.0f)); return sigma_rod; } float sigma_response_cone(float I) { //!! INFO: this is essentially the same as below (formulas come from // R.W.G.Hunt book) // float k = 1.0f/(5.0f*I+1); // float fl = 0.2f*5.0f*pow(k,4.0f)*I // + 0.1f*pow(1.0f-pow(k,4.0f),2.0f)*pow(5*I,1.0f/3.0f); // float sigma_cone = pow(2.0f,1.0f/n) / fl * (5.0f*I); float k = 1.0f/(5.0f*I+1); float k4 = pow(k,4.0f); float sigma_cone = (12.9223f*I) / ( k4*I+0.171 * pow(1.0f-k4,2) * powf(I,1.0f/3.0f)); return sigma_cone; } float model_response(float I, float sigma) { return pow(I,n)/(pow(I,n)+pow(sigma,n)); } /////////////////////////////////////////////////////////// /** * @brief Calculate local adaptation for specified pixel coordinates * * Calculation based on article "Adaptive Gain Control" by Pattanaik * 2002. * * @param Y luminance map * @param x x-coordinate of pixel * @param y x-coordinate of pixel * @param Acone [out] calculated adaptation for cones * @param Arod [out] calculated adaptation for rods */ void calculateLocalAdaptation(const pfstmo::Array2D* Y, int x, int y, float& Acone, float& Arod) { int width = Y->getCols(); int height = Y->getRows(); int kernel_size = 4; static float LOG5 = log(5); float logLc = log((*Y)(x,y))/LOG5; float pix_num = 0.0; float pix_sum = 0.0; for( int kx = -kernel_size ; kx <= kernel_size ; kx++ ) for( int ky = -kernel_size ; ky <= kernel_size ; ky++ ) if( (kx*kx+ky*ky)<=(kernel_size*kernel_size) && x+kx>0 && x+kx0 && y+ky0.0 ) Acone = Arod = (pix_sum / pix_num); else Acone = Arod = (*Y)(x,y); } /////////////////////////////////////////////////////////// VisualAdaptationModel::VisualAdaptationModel() { setAdaptation(60.0f, 60.0f); } void VisualAdaptationModel::calculateAdaptation(float Gcone, float Grod, float dt) { // Visual adaptation model for cones const float t0cone=0.080f; const float t0rod=0.150f; const float tau_cone=110.0f; const float tau_rod=400.0f; float in,out; float delta_out; float f,j,k; // Calculation of Acone & Arod // Acone in = Gcone; out = Acone; delta_out= in-out; f = (1.0f - exp( -dt/t0cone ) ); Acone += f*delta_out; // Arod in = Grod; out = Arod; delta_out= in-out; f = (1.0f - exp( -dt/t0rod ) ); Arod += f*delta_out; // Calculation of Bcone & Brod // Bcone in = Gcone; out = Bcone; j = dt * in * out / 2.2e8; k = dt * (1.0f - out ) / tau_cone; f = k-j; Bcone += f; // Brod in = Grod; out = Brod; j = dt * in * out / 16; k = dt * (1.0f - out ) / tau_rod; f = k-j; Brod += f; } void VisualAdaptationModel::calculateAdaptation(pfs::Array2D *Y, float dt) { float Acone = calculateLogAvgLuminance(Y); calculateAdaptation(Acone, Acone, dt); } void VisualAdaptationModel::setAdaptation(float Gcone, float Grod) { Acone = Gcone; Arod = Grod; Bcone = 2e6/(2e6+Acone); Brod = 0.04f/(0.04f+Arod); } void VisualAdaptationModel::setAdaptation(pfs::Array2D *Y) { float Acone = calculateLogAvgLuminance(Y) * 5.0f; setAdaptation(Acone, Acone); } float VisualAdaptationModel::calculateLogAvgLuminance( pfs::Array2D* Y ) { float avLum = 0.0f; int size = Y->getCols() * Y->getRows(); for( int i=0 ; i * * $Id: pfstmo.h,v 1.4 2012/03/24 23:00:35 tk255 Exp $ */ #ifndef PFSTMO_H #define PFSTMO_H #include #include /* Common return codes for operators */ #define PFSTMO_OK 1 /* Successful */ #define PFSTMO_ABORTED -1 /* User aborted (from callback) */ #define PFSTMO_ERROR -2 /* Failed, encountered error */ /* Return codes for the progress_callback */ #define PFSTMO_CB_CONTINUE 1 #define PFSTMO_CB_ABORT -1 /** * This function is called from a tone-mapper to report current progress. * * @param progress the current progress in percent (grows from 0 to 100) * @return return PFSTMO_CB_CONTINUE to continue tone-mapping or * PFSTMO_CB_ABORT to request to stop. In some cases tone-mapping may * not stop immediately, even if PFSTMO_CB_ABORT was returned. */ typedef int(*pfstmo_progress_callback)(int progress); namespace pfstmo { template class Array2DBase { T* data; unsigned int width; unsigned int height; bool dataOwned; public: Array2DBase(unsigned int width, unsigned int height): data(new T[width * height]), width(width), height(height), dataOwned(true) { } Array2DBase(unsigned int width, unsigned int height, T* data): data(data), width(width), height(height), dataOwned(false) { } Array2DBase(const Array2DBase& other): data(other.data), width(other.width), height(other.height), dataOwned(false) { } ~Array2DBase() { if (dataOwned) delete[] data; } Array2DBase& operator = (const Array2DBase& other) { if (dataOwned) delete[] data; this->data = other.data; this->width = other.width; this->height = other.height; this->dataOwned = false; return *this; } void allocate(unsigned int width, unsigned int height) { if (dataOwned) delete[] data; this->width = width; this->height = height; this->data = new T[width * height]; this->dataOwned = true; } inline T operator () (unsigned int x, unsigned int y) const { assert(x >= 0 && x < width && y >= 0 && y < height); return data[y * width + x]; } inline T& operator () (unsigned int x, unsigned int y) { assert(x >= 0 && x < width && y >= 0 && y < height); return data[y * width + x]; } inline T operator () (unsigned int index) const { assert(index >= 0 && index < width * height); return data[index]; } inline T& operator () (unsigned int index) { assert(index >= 0 && index < width * height); return data[index]; } unsigned int getCols() const { return width; } unsigned int getRows() const { return height; } const T* getRawData() const { return data; } T* getRawData() { return data; } }; template void copyArray(const Array2DBase *from, Array2DBase *to) { assert( from->getRows() == to->getRows() ); assert( from->getCols() == to->getCols() ); memcpy(to->getRawData(), from->getRawData(), from->getRows() * from->getCols() * sizeof(T)); } template void setArray(Array2DBase *array, T value) { const int elements = array->getRows()*array->getCols(); for( int i = 0; i < elements; i++ ) (*array)(i) = value; } typedef Array2DBase Array2D; typedef Array2DBase Array2Dd; } #endif pfstools-2.2.0/src/tmo/ferradans11/0002775000701400070140000000000014105165615015561 5ustar rkm38rkm38pfstools-2.2.0/src/tmo/ferradans11/Imagen.cpp0000644000701400070140000001251614105165615017466 0ustar rkm38rkm38#include "Imagen.h" Imagen::Imagen() { dim[0]=0; dim[1]=0; datos=new float[1]; } Imagen::Imagen(Imagen & im2) { dim[0]=im2.fils(); dim[1]=im2.cols(); int largo=dim[0]*dim[1]; datos=new float[largo]; for(int i=0; i< largo; i++) datos[i]=im2.datos[i]; } Imagen::Imagen(int fil, int col) { dim[0]=fil; dim[1]=col; datos=new float[fil*col]; } Imagen::Imagen(int fil, int col, float val) { dim[0]=fil; dim[1]=col; datos=new float[fil*col]; for(int i=0; idatos[i] *= (&im2)->datos[i]; return; } void Imagen::operator/=(Imagen & im2) { // si el divisor es 0, pone 0 en el cociente... int fil2=im2.fils(); int col2=im2.cols(); if(dim[0]!=fil2 || dim[1]!=col2) { fprintf(stderr,"Diferentes dimensiones al multiplicar imagenes \n"); return; } int largo=dim[0]*dim[1]; double div; for(int i=0; i1e-10) datos[i]/=div; else datos[i]=0.0; } return; } void Imagen::operator=(Imagen & im2) { // este operador no duplica la memoria // int fil2=im2.fils(); int col2=im2.cols(); if(dim[0]!=fil2 || dim[1]!=col2) { // fprintf(stderr,"Diferentes dimensiones al copiar imagenes \n"); dim[0]=fil2; dim[1]=col2; delete[] datos; datos=new float[fil2*col2]; } int largo=dim[0]*dim[1]; for(int i=0; imaxval(); float m=this->minval(); float R; float eps=1e-6; float s=(maxv-minv)/(M-m); for(int i=0;i #include #include #include "pfstmo.h" #include "Imagen.h" #define PROG_NAME "pfstmo_ferradans11" /// verbose mode bool verbose = false; class QuietException { }; void printHelp() { fprintf( stderr, PROG_NAME " (" PACKAGE_STRING ") : \n" "\t[--rho ] controls the overall intensity of the final output, the bigger the value the darker the image. Its range is approx (-10,10), recommended 0, although it might depend on the image. \n" "\t[--inv_alpha ] related to the contrast resolution. The bigger the more local contrast. For a good contrast resolution we suggest the value 20. Valid values:(0.1,100)\n" "See man page for more information.\n" ); } void pfstmo_ferradans11( int argc, char* argv[] ) { pfs::DOMIO pfsio; //--- default tone mapping parameters; float rho = -2; float inv_alpha = 5; static struct option cmdLineOptions[] = { { "help", no_argument, NULL, 'h' }, { "rho", required_argument, NULL, 'r' }, { "inv_alpha", required_argument, NULL, 'a' }, { NULL, 0, NULL, 0 } }; static const char optstring[] = "hr:a:"; int optionIndex = 0; while( 1 ) { int c = getopt_long (argc, argv, optstring, cmdLineOptions, &optionIndex); if( c == -1 ){ break;} switch( c ) { case 'h': printHelp(); throw QuietException(); case 'v': verbose = true; break; case 'r': rho = (float)strtod( optarg, NULL ); break; case 'a': inv_alpha = (float)strtod( optarg, NULL ); if( inv_alpha<=0.0f ) throw pfs::Exception("inv_alpha value out of range, should be >0"); break; case '?': printHelp(); throw QuietException(); case ':': printHelp(); throw QuietException(); } } while( true ) { pfs::Frame *frame = pfsio.readFrame( stdin ); if( frame == NULL ) break; // No more frames pfs::Channel *X, *Y, *Z; frame->getXYZChannels( X, Y, Z ); frame->getTags()->setString("LUMINANCE", "RELATIVE"); //--- if( Y==NULL || X==NULL || Z==NULL) throw pfs::Exception( "Missing X, Y, Z channels in the PFS stream" ); // tone mapping int w = Z->getCols(); int h = Z->getRows(); // in-place color space transform pfs::Array2DImpl* R = new pfs::Array2DImpl(w,h); pfs::Array2DImpl* G = new pfs::Array2DImpl(w,h); pfs::Array2DImpl* B = new pfs::Array2DImpl(w,h); pfs::transformColorSpace( pfs::CS_XYZ, X, Y, Z, pfs::CS_RGB, R, G, B ); tmo_ferradans11(w, h, R->getRawData(), G->getRawData(), B->getRawData(), rho, inv_alpha); pfs::transformColorSpace( pfs::CS_SRGB, R, G, B, pfs::CS_XYZ, X, Y, Z ); delete R; delete G; delete B; //--- pfsio.writeFrame( frame, stdout ); pfsio.freeFrame( frame ); } } int main( int argc, char* argv[] ) { try { pfstmo_ferradans11( argc, argv ); } catch( pfs::Exception ex ) { fprintf( stderr, PROG_NAME " error: %s\n", ex.getMessage() ); return EXIT_FAILURE; } catch( QuietException ex ) { return EXIT_FAILURE; } return EXIT_SUCCESS; } pfstools-2.2.0/src/tmo/ferradans11/Imagen.h0000644000701400070140000000412414105165615017127 0ustar rkm38rkm38#ifndef IMAGEN_H #define IMAGEN_H #include #include #include #define MINDOUBLE 1.0e-2 #include using namespace std; inline int signo(double a) { if(a!=0.0) if(a>0) return(1); else return(-1); else return(0); } ; inline double max(double a, double b) { if(a<=b) return(b); else return(a); }; inline double min(double a, double b) { if(a>=b) return(b); else return(a); }; inline float minmod(float x,float y) { return(signo(x)*max(0,min(fabs(x),y*signo(x)))); }; inline double mediana(float a, float b, float c) { if(a <= b) if(c<=a) return(a); else if(c<=b) return(c); else return(b); else // b < a if(c<=b) return(b); else if(c<=a) return(c); else return(a); } class Imagen { public: int dim[2]; float * datos; Imagen(); Imagen(Imagen & im2); Imagen(int fil, int col); Imagen(int fil, int col, float val); ~Imagen(); void operator*=(float dt); void operator+=(float val); void operator+=(Imagen & im2); void operator-=(Imagen & im2); void operator*=(Imagen & im2); void operator/=(Imagen & im2); void operator=(Imagen & im2); int fils(){return dim[0];} int cols(){return dim[1];} int area(){return dim[0]*dim[1];} inline float & operator()(int i, int j) { return(datos[i*dim[1]+j]); }; void setvalues(int fil, int col, float* val); float maxval(); float maxabsval(); float minval(); float medval(); void recorta(float M,float m); void escala(float p, float q); // fftw_complex * fft(rfftwnd_plan p); void fftshift(); float MSE(Imagen & Im2, float escala); Imagen & minmod(Imagen & c); }; void tmo_ferradans11(int w, int h, float *R,float* G, float* B,float rho, float invalpha); void producto(fftwf_complex* A, fftwf_complex* B,fftwf_complex* AB, int fil, int col); //fftw_complex * producto(fftw_complex* A, fftw_complex* B, int fil, int col); Imagen & nucleo_gaussiano(int fil, int col, float sigma); int compara_dims(Imagen & im1, Imagen & im2); #endif pfstools-2.2.0/src/tmo/ferradans11/tmo_ferradans11.cpp0000664000701400070140000003550614105165615021262 0ustar rkm38rkm38/** * @file tmo_ferradans11.cpp * Implementation of the algorithm presented in : * * An Analysis of Visual Adaptation and Contrast Perception for Tone Mapping * S. Ferradans, M. Bertalmio, E. Provenzi, V. Caselles * In IEEE Trans. Pattern Analysis and Machine Intelligence * * * @author Sira Ferradans Copyright (C) 2013 * * * This file is a part of PFSTMO package. * ---------------------------------------------------------------------- * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * */ #include #include #include #include #include #include #include "Imagen.h" #include "pfstmo.h" #include #include #include #include using namespace std; //for debugging purposes #if 0 #define PFSEOL "\x0a" static void dumpPFS( const char *fileName, const pfstmo::Array2D *data, const char *channelName ) { FILE *fh = fopen( fileName, "wb" ); assert( fh != NULL ); int width = data->getCols(); int height = data->getRows(); fprintf( fh, "PFS1" PFSEOL "%d %d" PFSEOL "1" PFSEOL "0" PFSEOL "%s" PFSEOL "0" PFSEOL "ENDH", width, height, channelName ); for( int y = 0; y < height; y++ ) for( int x = 0; x < width; x++ ) { float d = (*data)(x,y); fwrite( &d, sizeof( float ), 1, fh ); } fclose( fh ); } #endif //-------------------------------------------------------------------- /*Implementation, hardcoded of the R function */ float apply_arctg_slope10(float Ip,float I,float I2,float I3,float I4,float I5,float I6,float I7) { //Arctan, slope 10 float gr1,gr2,gr3,gr4,gr5,gr6,gr7; gr1=Ip-I; gr2=Ip*Ip-2*Ip*I+I2; gr3=Ip*Ip*Ip-3*Ip*Ip*I+3*Ip*I2-I3; gr4=Ip*Ip*Ip*Ip+4*Ip*Ip*I2+I4-4*Ip*I3+2*Ip*Ip*(I2-2*Ip*I); gr5=Ip*gr4-Ip*Ip*Ip*Ip*I-4*Ip*Ip*I3-I5+4*Ip*I4-2*Ip*Ip*(I3-2*Ip*I2); gr6=Ip*Ip*gr4-Ip*Ip*Ip*Ip*Ip*I-4*Ip*Ip*Ip*I3-Ip*I5+4*Ip*Ip*I4-2*Ip*Ip*Ip*(I3-2*Ip*I2)-(I*Ip*gr4-Ip*Ip*Ip*Ip*I2-4*Ip*Ip*I4-I6+4*Ip*I5-2*Ip*Ip*(I4-2*Ip*I3) ); gr7=Ip*Ip*(Ip*( Ip*Ip*Ip*Ip+4*Ip*Ip*I2+I4-4*Ip*I3+2*Ip*Ip*(I2-2*Ip*I) )-Ip*Ip*Ip*Ip*I-4*Ip*Ip*I3-I5+4*Ip*I4-2*Ip*Ip*(I3-2*Ip*I2))-2*Ip*(Ip*(Ip*Ip*Ip*Ip*I+4*Ip*Ip*I3+I5-4*Ip*I4+2*Ip*Ip*(I3-2*Ip*I2)) -Ip*Ip*Ip*Ip*I2-4*Ip*Ip*I4-I6+4*Ip*I5-2*Ip*Ip*(I4-2*Ip*I3))+Ip*(Ip*Ip*Ip*Ip*I2+4*Ip*Ip*I4+I6-4*Ip*I5+2*Ip*Ip*(I4-2*Ip*I3)) -Ip*Ip*Ip*Ip*I3-4*Ip*Ip*I5-I7+4*Ip*I6-2*Ip*Ip*(I5-2*Ip*I4); // Hardcoded coefficients of the polynomial of degree 9 that allows to approximate the neighborhood averaging as a sum of convolutions return(( -7.7456e+00)*gr7+ (3.1255e-16)*gr6+(1.5836e+01)*gr5+(-1.8371e-15)*gr4+(-1.1013e+01)*gr3+(4.4531e-16)*gr2+(3.7891e+00)*gr1+ 1.2391e-15 ) ; } /* * This Quickselect routine is based on the algorithm described in * "Numerical recipes in C", Second Edition, * Cambridge University Press, 1992, Section 8.5, ISBN 0-521-43108-5 * This code by Nicolas Devillard - 1998. Public domain. */ #define ELEM_SWAP(a,b) { register float t=(a);(a)=(b);(b)=t; } //#define ELEM_SWAP(a,b) { register float t=a;a=b;b=t; } double quick_select(float arr[], int n) { int low, high ; int median; int middle, ll, hh; low = 0 ; high = n-1 ; median = (low + high) / 2; for (;;) { if (high <= low) /* One element only */ return arr[median] ; if (high == low + 1) { /* Two elements only */ if (arr[low] > arr[high]) ELEM_SWAP(arr[low], arr[high]) ; return arr[median] ; } /* Find median of low, middle and high items; swap into position low */ middle = (low + high) / 2; if (arr[middle] > arr[high]) ELEM_SWAP(arr[middle], arr[high]) ; if (arr[low] > arr[high]) ELEM_SWAP(arr[low], arr[high]) ; if (arr[middle] > arr[low]) ELEM_SWAP(arr[middle], arr[low]) ; /* Swap low item (now in position middle) into position (low+1) */ ELEM_SWAP(arr[middle], arr[low+1]) ; /* Nibble from each end towards middle, swapping items when stuck */ ll = low + 1; hh = high; for (;;) { do ll++; while (arr[low] > arr[ll]) ; do hh--; while (arr[hh] > arr[low]) ; if (hh < ll) break; ELEM_SWAP(arr[ll], arr[hh]) ; } /* Swap middle item (in position low) back into correct position */ ELEM_SWAP(arr[low], arr[hh]) ; /* Re-set active partition */ if (hh <= median) low = ll; if (hh >= median) high = hh - 1; } } #undef ELEM_SWAP void tmo_ferradans11(int col,int fil,float* imR, float* imG, float* imB,float rho, float invalpha){ int length=fil*col; int colors=3; float lambda; float dt=0.2;//1e-1;// float threshold_diff=dt/20.0;//1e-5;// Imagen RGBorig[3],*pIm; pIm = new Imagen(fil,col); for (int c =0;c<3;c++){ RGBorig[c] = *pIm; } for(int i=0;ithreshold_diff) { iteration++; difference0=difference; difference=0.0; for(int color=0;color] [--inv_alpha-a ] [--help] .SH DESCRIPTION This command implements a tone mapping operator as described in: .PD 0 .IP S. Ferradans, M. Bertalmio, E. Provenzi and V. Caselles .IP An analysis of visual adaptation and contrast perception for tone mapping. .IP In: Trans. on Pattern Analysis and Machine Intelligence, 2011. .PD .PP Note that this operator does NOT require gamma correction. See the example below. .PP .SH OPTIONS .TP \fB--rho-r\fR , \fB-r\fR Controls over all lightness(related to the adaptation level), the greater the value the brighter the final image. Default value: -4 .TP \fB--inv_alpha-a\fR , \fB-a\fR Controls the level of detail, the higher the value the bigger the level of detail enhancement. Default value: 0.1 .TP \fB--verbose\fR Print additional information during program execution. .TP \fB--help\fR, \fB-h\fR Print list of commandline options. .SH EXAMPLES .TP pfsin Tree.pfm | pfstmo_ferradans11 --rho -3 --inv_alpha 10 | pfsout tree.png Tone map image and save it in png format. .SH "SEE ALSO" .BR pfsgamma (1) .BR pfsin (1) .BR pfsout (1) .BR pfsview (1) .SH BUGS Please report bugs and comments to the pfstools discussion group (http://groups.google.com/group/pfstools). pfstools-2.2.0/src/tmo/CMakeLists.txt0000664000701400070140000000055414105165615016214 0ustar rkm38rkm38add_subdirectory (drago03) add_subdirectory (mantiuk06) add_subdirectory (reinhard02) add_subdirectory (reinhard05) add_subdirectory (pattanaik00) add_subdirectory (fattal02) add_subdirectory (durand02) add_subdirectory (mai11) if( FFTW_FOUND ) add_subdirectory (ferradans11) endif( FFTW_FOUND ) if( GSL_FOUND ) add_subdirectory (mantiuk08) endif( GSL_FOUND ) pfstools-2.2.0/src/tmo/durand02/0002775000701400070140000000000014105165615015071 5ustar rkm38rkm38pfstools-2.2.0/src/tmo/durand02/fastbilateral.h0000664000701400070140000000323014105165615020053 0ustar rkm38rkm38/** * @file fastbilateral.h * @brief Fast bilateral filtering * * * This file is a part of PFSTMO package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Grzegorz Krawczyk, * * $Id: fastbilateral.h,v 1.3 2008/09/09 00:56:49 rafm Exp $ */ #ifndef _fastbilateral_h_ #define _fastbilateral_h_ #include /** * @brief Fast bilateral filtering * * Pieceweise linear algorithm (fast). * * @param I [in] input array * @param J [out] filtered array * @param sigma_s sigma value for spatial kernel * @param sigma_r sigma value for range kernel */ void fastBilateralFilter( const pfstmo::Array2D *I, pfstmo::Array2D *J, float sigma_s, float sigma_r, int downsample, pfstmo_progress_callback progress_cb ); #endif /* #ifndef _fastbilateral_h_ */ pfstools-2.2.0/src/tmo/durand02/fastbilateral.cpp0000664000701400070140000002247114105165615020416 0ustar rkm38rkm38/** * @file fastbilateral.cpp * @brief Fast bilateral filtering * * * This file is a part of PFSTMO package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Grzegorz Krawczyk, * * $Id: fastbilateral.cpp,v 1.5 2008/09/09 18:10:49 rafm Exp $ */ #include #include #include #include #include "pfstmo.h" using namespace std; inline float max( float a, float b ) { return a > b ? a : b; } inline float min( float a, float b ) { return a < b ? a : b; } // TODO: use spatial convolution rather than FFT, should be much // faster except for a very large kernels class GaussianBlur { float* source; fftwf_complex* freq; fftwf_plan fplan_fw; fftwf_plan fplan_in; float sigma; public: GaussianBlur( int nx, int ny, float sigma ) : sigma( sigma ) { int ox = nx; int oy = ny/2 + 1; // saves half of the data const int osize = ox * oy; source = (float*)fftwf_malloc(sizeof(float) * nx * 2 * (ny/2+1) ); freq = (fftwf_complex*) fftwf_malloc(sizeof(fftwf_complex) * osize); // if( source == NULL || freq == NULL ) //TODO: throw exception fplan_fw = fftwf_plan_dft_r2c_2d(nx, ny, source, freq, FFTW_ESTIMATE); fplan_in = fftwf_plan_dft_c2r_2d(nx, ny, freq, source, FFTW_ESTIMATE); } void blur( const pfstmo::Array2D *I, pfstmo::Array2D *J ) { int i,x,y; int nx = I->getCols(); int ny = I->getRows(); int nsize = nx * ny; int ox = nx; int oy = ny/2 + 1; // saves half of the data int osize = ox * oy; for( y=0 ; ygetCols() / (float)out->getCols(); float dy = (float)in->getRows() / (float)out->getRows(); float pad; float filterSamplingX = max( modff( dx, &pad ), 0.01f ); float filterSamplingY = max( modff( dy, &pad ), 0.01f ); const int outRows = out->getRows(); const int outCols = out->getCols(); const float inRows = in->getRows(); const float inCols = in->getCols(); const float filterSize = 1; float sx, sy; int x, y; for( y = 0, sy = -0.5 + dy/2; y < outRows; y++, sy += dy ) for( x = 0, sx = -0.5 + dx/2; x < outCols; x++, sx += dx ) { float pixVal = 0; float weight = 0; for( float ix = max( 0, ceilf( sx-filterSize ) ); ix <= min( floorf(sx+filterSize), inCols-1 ); ix++ ) for( float iy = max( 0, ceilf( sy-filterSize ) ); iy <= min( floorf( sy+filterSize), inRows-1 ); iy++ ) { float fx = fabs( sx - ix ); float fy = fabs( sy - iy ); const float fval = (1.0f-fx)*(1.0f-fy); pixVal += (*in)( (int)ix, (int)iy ) * fval; weight += fval; } if( weight == 0 ) { fprintf( stderr, "%g %g %g %g\n", sx, sy, dx, dy ); } // assert( weight != 0 ); (*out)(x,y) = pixVal / weight; } } void downsampleArray( const pfstmo::Array2D *in, pfstmo::Array2D *out ) { const float inRows = in->getRows(); const float inCols = in->getCols(); const int outRows = out->getRows(); const int outCols = out->getCols(); const float dx = (float)in->getCols() / (float)out->getCols(); const float dy = (float)in->getRows() / (float)out->getRows(); const float filterSize = 0.5; float sx, sy; int x, y; for( y = 0, sy = dy/2-0.5; y < outRows; y++, sy += dy ) for( x = 0, sx = dx/2-0.5; x < outCols; x++, sx += dx ) { float pixVal = 0; float w = 0; for( float ix = max( 0, ceilf( sx-dx*filterSize ) ); ix <= min( floorf( sx+dx*filterSize ), inCols-1 ); ix++ ) for( float iy = max( 0, ceilf( sy-dx*filterSize ) ); iy <= min( floorf( sy+dx*filterSize), inRows-1 ); iy++ ) { pixVal += (*in)( (int)ix, (int)iy ); w += 1; } (*out)(x,y) = pixVal/w; } } #endif /* Pseudocode from the paper: PiecewiseBilateral (Image I, spatial kernel fs , intensity influence gr ) J=0 // set the output to zero for j=0..NB SEGMENTS ij= minI+j.*(max(I)-min(I))/NB SEGMENTS Gj=gr (I - ij ) // evaluate gr at each pixel Kj=Gj x fs // normalization factor Hj=Gj .* I // compute H for each pixel Hj=Hj x fs Jj=Hj ./ Kj // normalize J=J+Jj .* InterpolationWeight(I, ij ) */ void fastBilateralFilter( const pfstmo::Array2D *I, pfstmo::Array2D *J, float sigma_s, float sigma_r, int downsample, pfstmo_progress_callback progress_cb ) { int i; int w = I->getCols(); int h = I->getRows(); int size = w * h; // find range of values in the input array float maxI = (*I)(0); float minI = (*I)(0); for(i=0 ; imaxI ) ) maxI = v; if( unlikely( v jI + stepI ) ) continue; // wi = 0; if( likely( (*I)(i) > jI ) ) { float wi = (stepI - ((*I)(i)-jI)) / stepI; (*J)(i) += (*JJ)(i)*wi; } else (*J)(i) += (*JJ)(i); } } else if( j == NB_SEGMENTS-1 ) { // if the last segment - to account for the range boundary for( i=0 ; i0.0f ) ) (*J)(i) += (*JJ)(i)*wi; } } } // delete Iz; // if( downsample != 1 ) // delete JJ; delete jJ; delete jG; delete jK; delete jH; } pfstools-2.2.0/src/tmo/durand02/bilateral.h0000664000701400070140000000327314105165615017204 0ustar rkm38rkm38/** * @file bilateral.h * @brief Bilateral filtering * * * This file is a part of PFSTMO package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Rafal Mantiuk, * @author Grzegorz Krawczyk, * * $Id: bilateral.h,v 1.3 2008/09/09 00:56:49 rafm Exp $ */ #ifndef _bilateral_h_ #define _bilateral_h_ /** * @brief Bilateral filtering * * Conventional (slow) algorithm. * * @param I [in] input array * @param J [out] filtered array * @param sigma_s sigma value for spatial kernel * @param sigma_r sigma value for range kernel * * $Id: bilateral.h,v 1.3 2008/09/09 00:56:49 rafm Exp $ */ void bilateralFilter( const pfstmo::Array2D *I, pfstmo::Array2D *J, float sigma_s, float sigma_r, pfstmo_progress_callback progress_cb ); #endif /* #ifndef _bilateral_h_ */ pfstools-2.2.0/src/tmo/durand02/CMakeLists.txt0000664000701400070140000000126214105165615017630 0ustar rkm38rkm38include_directories ("${PROJECT_BINARY_DIR}/" "${PROJECT_SOURCE_DIR}/src/pfs" "${PROJECT_SOURCE_DIR}/src/tmo/pfstmo") if( NOT HAS_GETOPT ) include_directories ("${GETOPT_INCLUDE}") endif( NOT HAS_GETOPT ) link_directories("${PROJECT_SOURCE_DIR}/src/pfs") if( FFTW_FOUND ) set( FFTW_LIBRARIES ${FFTW_LIBS} ) set( FAST_BILATERAL "fastbilateral.cpp" ) else( FFTW_FOUND ) set( FFTW_LIBRARIES ) endif( FFTW_FOUND ) set(TRG pfstmo_durand02) add_executable(${TRG} ${TRG}.cpp tmo_durand02.cpp bilateral.cpp "${FAST_BILATERAL}" "${GETOPT_OBJECT}") target_link_libraries(${TRG} pfs ${FFTW_LIBRARIES}) install (TARGETS ${TRG} DESTINATION bin) install (FILES ${TRG}.1 DESTINATION ${MAN_DIR}) pfstools-2.2.0/src/tmo/durand02/pfstmo_durand02.10000664000701400070140000000403314105165615020160 0ustar rkm38rkm38.TH "pfstmo_durand02" 1 .SH NAME pfstmo_durand02 \- Fast bilateral filtering for the display of HDR images .SH SYNOPSIS .B pfstmo_durand02 [--sigma-s ] [--sigma-r ] [--base-contrast ] [--quiet] [--verbose] [--help] .SH DESCRIPTION This command implements a tone mapping operator as described in: .PD 0 .IP F. Durand and J. Dorsey. .IP Fast Bilateral Filtering for the Display of High-Dynamic-Range Images. .IP In: In ACM Transactions on Graphics, 2002. .PD .PP According to paper, results of this TMO require gamma correction. .PP .PD 0 Unless \fB-g\fR option is specified, this operator also employs color correction mechanism from: .IP Radoslaw Mantiuk, Rafal Mantiuk, Anna Tomaszewska, Wolfgang Heidrich. .IP Color Correction for Tone Mapping. .IP In: Computer Graphics Forum (Proc. of EUROGRAPHICS'09), 28(2), 2009. .IP http://zgk.wi.ps.pl/color_correction/ .PD .SH OPTIONS .TP \fB--sigma-s\fR , \fB-s\fR Sigma value for spatial kernel. Default value: 40 .TP \fB--sigma-r\fR , \fB-r\fR Sigma value for range kernel. Default value: 0.4 .TP \fB--base-contrast\fR , \fB-c\fR Contrast of the base layer. Default value is 5.0f. Lower value causes higher contrast compression and results in darker picture - can help if after tone mapping too many pixels remain saturated. .IP Increasing this value will results in brighter and more 'dynamic' picture. .TP \fB--original\fR, \fB-g\fR Use original algorithm as described in the paper with no extensions. For this operator the switch will disable color correction. .TP \fB--verbose\fR Print additional information during program execution. .TP \fB--quiet\fR, \fB-q\fR Do not display progress report. .TP \fB--help\fR, \fB-h\fR Print list of commandline options. .SH EXAMPLES .TP pfsin memorial.hdr | pfstmo_durand02 | pfsgamma -g 2.2 | pfsout memorial.png Tone map image and save it in png format. .SH "SEE ALSO" .BR pfsgamma (1) .BR pfsin (1) .BR pfsout (1) .BR pfsview (1) .SH BUGS Please report bugs and comments on implementation to Grzegorz Krawczyk . pfstools-2.2.0/src/tmo/durand02/pfstmo_durand02.cpp0000664000701400070140000001405314105165615020605 0ustar rkm38rkm38/** * @file pfstmo_durand02.cpp * @brief Tone map XYZ channels using Durand02 model * * Fast Bilateral Filtering for the Display of High-Dynamic-Range Images. * F. Durand and J. Dorsey. * In ACM Transactions on Graphics, 2002. * * * This file is a part of PFSTMO package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Grzegorz Krawczyk, * * $Id: pfstmo_durand02.cpp,v 1.5 2009/02/23 19:09:41 rafm Exp $ */ #include #include #include #include #include #include #include #include "tmo_durand02.h" using namespace std; #define PROG_NAME "pfstmo_durand02" /// verbose mode bool verbose = false; bool quiet = false; #define FRAME_NAME_MAX 30 char frame_name[FRAME_NAME_MAX+1]; int progress_report( int progress ) { if( !quiet ) { fprintf( stderr, "\r'%s' completed %d%%", frame_name, progress ); if( progress == 100 ) fprintf( stderr, "\n" ); } return PFSTMO_CB_CONTINUE; } class QuietException { }; void printHelp() { fprintf( stderr, PROG_NAME " (" PACKAGE_STRING ") : \n" "\t[--sigma-s ] [--sigma-r ] \n" "\t[--base-contrast ] \n" "\t[--original] \n" "\t[--quiet] [--verbose] [--help] \n" "See man page for more information.\n" ); } void pfstmo_durand02( int argc, char* argv[] ) { pfs::DOMIO pfsio; //--- default tone mapping parameters; #ifdef HAVE_FFTW3F float sigma_s = 40.0f; #else float sigma_s = 8.0f; #endif float sigma_r = 0.4f; float baseContrast = 5.0f; int downsample=1; bool original_algorithm = false; static struct option cmdLineOptions[] = { { "help", no_argument, NULL, 'h' }, { "verbose", no_argument, NULL, 'v' }, { "original", no_argument, NULL, 'g' }, { "sigma-s", required_argument, NULL, 's' }, { "sigma-r", required_argument, NULL, 'r' }, { "base-contrast", required_argument, NULL, 'c' }, // { "downsampling", required_argument, NULL, 'z' }, { "quiet", no_argument, NULL, 'q' }, { NULL, 0, NULL, 0 } }; int optionIndex = 0; while( 1 ) { int c = getopt_long (argc, argv, "hvs:r:c:qg", cmdLineOptions, &optionIndex); if( c == -1 ) break; switch( c ) { case 'q': quiet = true; break; case 'h': printHelp(); throw QuietException(); case 'v': verbose = true; break; case 'g': original_algorithm = true; break; case 's': sigma_s = (float)strtod( optarg, NULL ); if( sigma_s<=0.0f ) throw pfs::Exception("sigma_s value out of range, should be >0"); break; case 'r': sigma_r = (float)strtod( optarg, NULL ); if( sigma_r<=0.0f ) throw pfs::Exception("sigma_r value out of range, should be >0"); break; case 'c': baseContrast = (float)strtod( optarg, NULL ); if( baseContrast<=0.0f ) throw pfs::Exception("base contrast value out of range, should be >0"); break; // case 'z': // downsample = atoi( optarg ); // if( downsample<1 || downsample>20 ) // throw pfs::Exception("down sampling factor should be in 1:20 range"); // break; case '?': throw QuietException(); case ':': throw QuietException(); } } VERBOSE_STR << "sigma_s: " << sigma_s << endl; VERBOSE_STR << "sigma_r: " << sigma_r << endl; VERBOSE_STR << "base contrast: " << baseContrast << endl; // VERBOSE_STR << "down sampling factor: " << downsample << endl; #ifdef HAVE_FFTW3F VERBOSE_STR << "fast bilateral filtering (fftw3)" << endl; #else VERBOSE_STR << "conventional bilateral filtering" << endl; #endif int frame_no = 1; while( true ) { pfs::Frame *frame = pfsio.readFrame( stdin ); if( frame == NULL ) break; // No more frames pfs::Channel *X, *Y, *Z; frame->getXYZChannels( X, Y, Z ); frame->getTags()->setString("LUMINANCE", "RELATIVE"); //--- if( Y==NULL || X==NULL || Z==NULL) throw pfs::Exception( "Missing X, Y, Z channels in the PFS stream" ); // tone mapping int w = Y->getCols(); int h = Y->getRows(); const char *file_name = frame->getTags()->getString( "FILE_NAME" ); if( file_name == NULL ) sprintf( frame_name, "frame #%d", frame_no ); else { int len = strlen( file_name ); if( len > FRAME_NAME_MAX ) // In case file name is too long len = FRAME_NAME_MAX-3; strcpy( frame_name, "..." ); strncpy( frame_name+3, file_name + strlen( file_name ) - len, len+1 ); } pfs::transformColorSpace( pfs::CS_XYZ, X, Y, Z, pfs::CS_RGB, X, Y, Z ); tmo_durand02( w, h, X->getRawData(), Y->getRawData(), Z->getRawData(), sigma_s, sigma_r, baseContrast, downsample, !original_algorithm, progress_report ); pfs::transformColorSpace( pfs::CS_RGB, X, Y, Z, pfs::CS_XYZ, X, Y, Z ); //--- pfsio.writeFrame( frame, stdout ); pfsio.freeFrame( frame ); frame_no++; } } int main( int argc, char* argv[] ) { try { pfstmo_durand02( argc, argv ); } catch( pfs::Exception ex ) { fprintf( stderr, PROG_NAME " error: %s\n", ex.getMessage() ); return EXIT_FAILURE; } catch( QuietException ex ) { return EXIT_FAILURE; } return EXIT_SUCCESS; } pfstools-2.2.0/src/tmo/durand02/tmo_durand02.h0000664000701400070140000000417214105165615017542 0ustar rkm38rkm38/** * @file tmo_durand02.h * @brief Local tone mapping operator based on bilateral filtering. * Durand et al. 2002 * * Fast Bilateral Filtering for the Display of High-Dynamic-Range Images. * F. Durand and J. Dorsey. * In ACM Transactions on Graphics, 2002. * * * This file is a part of PFSTMO package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Grzegorz Krawczyk, * * $Id: tmo_durand02.h,v 1.4 2009/02/23 19:09:41 rafm Exp $ */ #ifndef _tmo_durand02_h_ #define _tmo_durand02_h_ #include /* * @brief Fast bilateral filtering * * @param width image width * @param height image height * @param R red channel * @param G green channel * @param B blue channel * @param sigma_s sigma for spatial kernel * @param sigma_r sigma for range kernel * @param baseContrast contrast of the base layer * @param color_correction enable automatic color correction * @param downsample down sampling factor for speeding up fast-bilateral (1..20) */ void tmo_durand02(unsigned int width, unsigned int height, float *R, float *G, float *B, float sigma_s, float sigma_r, float baseContrast, int downsample, const bool color_correction = true, pfstmo_progress_callback progress_cb = NULL ); #endif /* _tmo_durand02_h_ */ pfstools-2.2.0/src/tmo/durand02/bilateral.cpp0000664000701400070140000000746114105165615017542 0ustar rkm38rkm38/** * @file bilateral.cpp * @brief Bilateral filtering * * * This file is a part of PFSTMO package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Rafal Mantiuk, * @author Grzegorz Krawczyk, * * $Id: bilateral.cpp,v 1.3 2008/09/09 00:56:49 rafm Exp $ */ #include #include #include "pfstmo.h" inline int max( int a, int b ) { return (a>b) ? a : b; } inline int min( int a, int b ) { return (agetRows(); y++ ) { for(unsigned int x = 0; x < kern->getCols(); x++ ) { float rx = (float)(x - kern->getCols()/2); float ry = (float)(y - kern->getRows()/2); float d2 = rx*rx + ry*ry; (*kern)(x, y) = (float)exp( -d2 / (2.*sigma*sigma) ); } } } class GaussLookup { float *gauss; float maxVal; float scaleFactor; int N; public: GaussLookup( float sigma, int N ) : N(N) { float sigma2 = sigma*sigma; maxVal = sqrtf(-log(0.01f)*2.0f*sigma2); gauss = new float[N]; for( int i = 0; i < N; i++ ) { float x = (float)i/(float)(N-1)*maxVal; gauss[i] = expf(-x*x/(2.0*sigma2)); } scaleFactor = (float)(N-1) / maxVal; } float getValue( float x ) { x = fabs( x ); if( unlikely( x > maxVal ) ) return 0; return gauss[ (int)(x*scaleFactor) ]; } }; void bilateralFilter( const pfstmo::Array2D *I, pfstmo::Array2D *J, float sigma_s, float sigma_r, pfstmo_progress_callback progress_cb ) { const pfstmo::Array2D *X1 = I; // intenisity data // x +- sigma_s*2 should contain 95% of the Gaussian distrib int sKernelSize = (int)( sigma_s*4 + 0.5 ) + 1; pfstmo::Array2D sKernel(sKernelSize, sKernelSize); gaussianKernel( &sKernel, sigma_s ); GaussLookup gauss( sigma_r, 256 ); for( int y = 0; y < I->getRows(); y++ ) { progress_cb( y * 100 / I->getRows() ); for( int x = 0; x < I->getCols(); x++ ) { float val = 0; float k = 0; float I_s = (*X1)(x,y); //!! previously 'I' not 'X1' if( unlikely( !isfinite( I_s ) ) ) I_s = 0.0f; for( int py = max( 0, y - sKernelSize/2); py < min( I->getRows(), y + sKernelSize/2); py++ ) { for( int px = max( 0, x - sKernelSize/2); px < min( I->getCols(), x + sKernelSize/2); px++ ) { float I_p = (*X1)(px, py); //!! previously 'I' not 'X1' if( unlikely( !isfinite( I_p ) ) ) I_p = 0.0f; float mult = sKernel(px-x + sKernelSize/2, py-y + sKernelSize/2) * gauss.getValue( I_p - I_s ); float Ixy = (*I)(px, py); if( unlikely( !isfinite( Ixy ) ) ) Ixy = 0.0f; val += Ixy*mult; //!! but here we want 'I' k += mult; } } //avoid division by 0 when k is close to 0 // (*J)(x,y) = fabs(k) > 0.00000001 ? val/k : 0.; (*J)(x,y) = val/k; } } } pfstools-2.2.0/src/tmo/durand02/tmo_durand02.cpp0000664000701400070140000001261514105165615020076 0ustar rkm38rkm38/** * @file tmo_bilateral.cpp * @brief Local tone mapping operator based on bilateral filtering. * Durand et al. 2002 * * Fast Bilateral Filtering for the Display of High-Dynamic-Range Images. * F. Durand and J. Dorsey. * In ACM Transactions on Graphics, 2002. * * * This file is a part of PFSTMO package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Grzegorz Krawczyk, * * $Id: tmo_durand02.cpp,v 1.6 2009/02/23 19:09:41 rafm Exp $ */ #include #include #include #include #include #include "pfstmo.h" //#undef HAVE_FFTW3F #ifdef HAVE_FFTW3F #include "fastbilateral.h" #else #include "bilateral.h" #endif static void findMaxMinPercentile(pfstmo::Array2D* I, float minPrct, float& minLum, float maxPrct, float& maxLum); /* From Durand's webpage: Here is the high-level set of operation that you need to do in order to perform contrast reduction input intensity= 1/61*(R*20+G*40+B) r=R/(input intensity), g=G/input intensity, B=B/input intensity log(base)=Bilateral(log(input intensity)) log(detail)=log(input intensity)-log(base) log (output intensity)=log(base)*compressionfactor+log(detail) R output = r*exp(log(output intensity)), etc. */ void tmo_durand02(unsigned int width, unsigned int height, float *nR, float *nG, float *nB, float sigma_s, float sigma_r, float baseContrast, int downsample, const bool color_correction, pfstmo_progress_callback progress_cb ) { pfstmo::Array2D* R = new pfstmo::Array2D(width, height, nR); pfstmo::Array2D* G = new pfstmo::Array2D(width, height, nG); pfstmo::Array2D* B = new pfstmo::Array2D(width, height, nB); int i; int w = R->getCols(); int h = R->getRows(); int size = w*h; pfstmo::Array2D* I = new pfstmo::Array2D(w,h); // intensities pfstmo::Array2D* BASE = new pfstmo::Array2D(w,h); // base layer pfstmo::Array2D* DETAIL = new pfstmo::Array2D(w,h); // detail layer float min_pos = 1e10f; // minimum positive value (to avoid log(0)) for( i=0 ; i 0) ) min_pos = (*I)(i); } for( i=0 ; i * * $Id: pfstmo_mantiuk08.cpp,v 1.19 2013/12/28 14:00:54 rafm Exp $ */ //#include #include #include #include #include #include #include #include #include #include #include #include #include #include "compression_tmo.h" #define PROG_NAME "pfstmo_mai11" class QuietException { }; class Timing { timeval t1; public: Timing() { gettimeofday(&t1, NULL); } void report( const char *activity ) { timeval t2; gettimeofday(&t2, NULL); unsigned int t = (t2.tv_sec - t1.tv_sec) * 1000000 + (t2.tv_usec - t1.tv_usec); fprintf( stderr, "Activity %s took %g seconds.\n", activity, (float)t / 1000000.f ); } }; using namespace std; const char *temp_file_2pass = "datmo_tone_curves.tmp"; void printHelp() { fprintf( stderr, PROG_NAME " (" PACKAGE_STRING ") : \n" "See man page for more information.\n" ); } bool verbose = false; bool quiet = false; #define FRAME_NAME_MAX 30 char frame_name[FRAME_NAME_MAX+1]; int progress_report( int progress ) { if( !quiet ) { fprintf( stderr, "\r'%s' completed %d%%", frame_name, progress ); if( progress == 100 ) fprintf( stderr, "\n" ); } return PFSTMO_CB_CONTINUE; } void tmo_mai11(int argc, char * argv[]) { //--- default tone mapping parameters; //--- process command line args static struct option cmdLineOptions[] = { { "help", no_argument, NULL, 'h' }, { "verbose", no_argument, NULL, 'v' }, { "quiet", no_argument, NULL, 'q' }, { NULL, 0, NULL, 0 } }; int optionIndex = 0; while( 1 ) { int c = getopt_long (argc, argv, "vhe:c:y:t:o:qm:f:a:", cmdLineOptions, &optionIndex); if( c == -1 ) break; switch( c ) { case 'h': printHelp(); throw QuietException(); case 'v': verbose = true; break; case 'q': quiet = true; break; } } Timing tm_entire; pfs::DOMIO pfsio; CompressionTMO tmo; size_t frame_no = 0; while( true ) { pfs::Frame *frame = pfsio.readFrame( stdin ); if( frame == NULL ) break; pfs::Channel *inX, *inY, *inZ; frame->getXYZChannels(inX, inY, inZ); int cols = frame->getWidth(); int rows = frame->getHeight(); const char *file_name = frame->getTags()->getString( "FILE_NAME" ); if( file_name == NULL ) sprintf( frame_name, "frame #%d", (int)frame_no ); else { int len = strlen( file_name ); if( len > FRAME_NAME_MAX ) // In case file name is too long len = FRAME_NAME_MAX-3; strcpy( frame_name, "..." ); strncpy( frame_name+3, file_name + strlen( file_name ) - len, len+1 ); } { pfs::Array2DImpl R( cols, rows ); pfs::transformColorSpace( pfs::CS_XYZ, inX, inY, inZ, pfs::CS_RGB, inX, &R, inZ ); tmo.tonemap( inX->getRawData(), R.getRawData(), inZ->getRawData(), cols, rows, inX->getRawData(), R.getRawData(), inZ->getRawData(), inY->getRawData(), progress_report ); progress_report( 100 ); pfs::transformColorSpace( pfs::CS_RGB, inX, &R, inZ, pfs::CS_XYZ, inX, inY, inZ ); frame->getTags()->setString("LUMINANCE", "DISPLAY"); pfsio.writeFrame( frame, stdout ); pfsio.freeFrame(frame); } frame_no++; } tm_entire.report( "Entire operation" ); } int main( int argc, char* argv[] ) { try { tmo_mai11( argc, argv ); } catch( pfs::Exception ex ) { fprintf( stderr, PROG_NAME " error: %s\n", ex.getMessage() ); return EXIT_FAILURE; } catch( QuietException ex ) { return EXIT_FAILURE; } return EXIT_SUCCESS; } pfstools-2.2.0/src/tmo/mai11/pfstmo_mai11.10000664000701400070140000000254714105165615016752 0ustar rkm38rkm38.TH "pfstmo_mai11" 1 .SH NAME pfstmo_mai11 \- Tone-mapping for backward-compatible compression .SH SYNOPSIS \fBpfstmo_mai11\fR [\fB--verbose\fR][\fB--quiet\fR] [\fB--help\fR] .SH DESCRIPTION This is a tone-mapping operator that is optimized to give the best performance for images or video that are encoded using a backward-compatible compression, such as HDR-JPEG, or HDR-MPEG. The images produced by this operator do not need to be visually pleasing. The details on the operator can be found in: .IP Mai, Z., Mansour, H., Mantiuk, R., Nasiopoulos, P., Ward, R., & Heidrich, W. .IP Optimizing a tone curve for backward-compatible high dynamic range image and video compression. .IP .PP If you find this TMO useful in your research project, please cite the paper above. .PP The result of this TMO does not require gamma correction. .SH OPTIONS .TP \fB--verbose\fR, \fB-v\fR Print additional information during program execution. .TP \fB--quiet\fR, \fB-q\fR Do not display progress report. .TP \fB--help\fR, \fB-h\fR Print list of commandline options. .SH EXAMPLES .TP pfsin memorial.hdr | pfstmo_mai11 | pfsout memorial_to_encode.png .IP Tone map memorial image and store the result in the PNG format. .SH "SEE ALSO" .BR pfsin (1) .BR pfsout (1) .BR pfsview (1) .SH BUGS Please report bugs and comments to the pfstools discussion group (http://groups.google.com/group/pfstools). pfstools-2.2.0/src/tmo/mai11/compression_tmo.cpp0000664000701400070140000001223514105165615020307 0ustar rkm38rkm38#include #include #include #include #include "compression_tmo.h" /** * Lookup table on a uniform array & interpolation * * x_i must be at least two elements * y_i must be initialized after creating an object */ class UniformArrayLUT { double start_v; size_t lut_size; double delta; bool own_y_i; public: double *y_i; UniformArrayLUT( double from, double to, int lut_size, double *y_i = NULL ) : start_v(from), lut_size( lut_size ), delta( (to-from)/(double)lut_size ) { if( y_i == NULL ) { this->y_i = new double[lut_size]; own_y_i = true; } else { this->y_i = y_i; own_y_i = false; } } UniformArrayLUT() : y_i(NULL), lut_size( 0 ), delta( 0. ), own_y_i( false ) {} UniformArrayLUT(const UniformArrayLUT& other) : start_v( other.start_v ), lut_size( other.lut_size ), delta( other.delta ) { this->y_i = new double[lut_size]; own_y_i = true; memcpy(this->y_i, other.y_i, lut_size * sizeof(double)); } UniformArrayLUT& operator = (const UniformArrayLUT& other) { this->lut_size = other.lut_size; this->delta = other.delta; this->start_v = other.start_v; this->y_i = new double[lut_size]; own_y_i = true; memcpy(this->y_i, other.y_i, lut_size * sizeof(double)); return *this; } ~UniformArrayLUT() { if( own_y_i ) delete []y_i; } double interp( double x ) { const double ind_f = (x - start_v)/delta; const size_t ind_low = (size_t)(ind_f); const size_t ind_hi = (size_t)ceil(ind_f); if( unlikely(ind_f < 0) ) // Out of range checks return y_i[0]; if( unlikely(ind_hi >= lut_size) ) return y_i[lut_size-1]; if( unlikely(ind_low == ind_hi) ) return y_i[ind_low]; // No interpolation necessary return y_i[ind_low] + (y_i[ind_hi]-y_i[ind_low])*(ind_f-(double)ind_low); // Interpolation } }; class ImgHistogram { public: const float L_min, L_max; const float delta; int *bins; double *p; int bin_count; ImgHistogram() : L_min( -6.f ), L_max( 9.f ), delta( 0.1 ), bins( NULL ), p( NULL ) { bin_count = (int)ceil((L_max-L_min)/delta); bins = new int[bin_count]; p = new double[bin_count]; } ~ImgHistogram() { delete [] bins; delete [] p; } void compute( const float *img, size_t pixel_count ) { std::fill( bins, bins + bin_count, 0 ); int pp_count = 0; for( size_t pp = 0; pp < pixel_count; pp++ ) { int bin_index = (img[pp]-L_min)/delta; // ignore anything outside the range if( bin_index < 0 || bin_index >= bin_count ) continue; bins[bin_index]++; pp_count++; } for( int bb = 0; bb < bin_count; bb++ ) { p[bb] = (double)bins[bb] / (double)pp_count; } } }; inline float safelog10f( float x ) { if( unlikely(x < 1e-5f) ) return -5.f; return log10f( x ); } void CompressionTMO::tonemap( const float *R_in, const float *G_in, float *B_in, int width, int height, float *R_out, float *G_out, float *B_out, const float *L_in, pfstmo_progress_callback progress_cb ) { const size_t pix_count = width*height; // Compute log of Luminance float *logL = new float[pix_count]; // std::unique_ptr logL(new float[pix_count]); for( size_t pp = 0; pp < pix_count; pp++ ) { logL[pp] = safelog10f( L_in[pp] ); } ImgHistogram H; H.compute(logL, pix_count ); //Compute slopes // std::unique_ptr s(new double[H.bin_count]); double *s = new double[H.bin_count]; { double d = 0; for( int bb = 0; bb < H.bin_count; bb++ ) { d += pow( H.p[bb], 1./3. ); } d *= H.delta; for( int bb = 0; bb < H.bin_count; bb++ ) { s[bb] = pow( H.p[bb], 1./3. )/d; } } #if 0 // TODO: Handling of degenerated cases, e.g. when an image contains uniform color const double s_max = 2.; // Maximum slope, to avoid enhancing noise double s_renorm = 1; for( int bb = 0; bb < H.bin_count; bb++ ) { if( s[bb] >= s_max ) { s[bb] = s_max; s_renorm -= s_max * H.delta; } } for( int bb = 0; bb < H.bin_count; bb++ ) { if( s[bb] < s_max ) { s[bb] = s_max; s_renorm -= s_max * H.delta; } } #endif progress_cb( 50 ); //Create a tone-curve UniformArrayLUT lut( H.L_min, H.L_max, H.bin_count ); lut.y_i[0] = 0; for( int bb = 1; bb < H.bin_count; bb++ ) { lut.y_i[bb] = lut.y_i[bb-1] + s[bb] * H.delta; } // Apply the tone-curve for( int pp = 0; pp < pix_count; pp++ ) { R_out[pp] = lut.interp( safelog10f(R_in[pp]) ); G_out[pp] = lut.interp( safelog10f(G_in[pp]) ); B_out[pp] = lut.interp( safelog10f(B_in[pp]) ); } delete [] s; delete [] logL; } pfstools-2.2.0/src/tmo/drago03/0002775000701400070140000000000014105165615014711 5ustar rkm38rkm38pfstools-2.2.0/src/tmo/drago03/pfstmo_drago03.cpp0000664000701400070140000001007214105165615020242 0ustar rkm38rkm38/** * @brief Adaptive logarithmic tone mapping * * Adaptive logarithmic mapping for displaying high contrast * scenes. * F. Drago, K. Myszkowski, T. Annen, and N. Chiba. In Eurographics 2003. * * This file is a part of PFSTMO package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Grzegorz Krawczyk, * * $Id: pfstmo_drago03.cpp,v 1.4 2009/04/15 11:49:32 julians37 Exp $ */ #include #include #include #include #include #include #include #include "tmo_drago03.h" #define PROG_NAME "pfstmo_drago03" using namespace std; class QuietException { }; void printHelp() { fprintf( stderr, PROG_NAME " (" PACKAGE_VERSION ") : \n" "\t [--bias ] [--verbose] [--help] \n" "See man page for more information. \n" ); } void tmo_drago03( int argc, char* argv[] ) { pfs::DOMIO pfsio; //--- default tone mapping parameters; float biasValue = 0.85f; //--- process command line args bool verbose = false; static struct option cmdLineOptions[] = { { "help", no_argument, NULL, 'h' }, { "verbose", no_argument, NULL, 'v' }, { "bias", required_argument, NULL, 'b' }, { NULL, 0, NULL, 0 } }; int optionIndex = 0; while( 1 ) { int c = getopt_long (argc, argv, "vhb:", cmdLineOptions, &optionIndex); if( c == -1 ) break; switch( c ) { case 'h': printHelp(); throw QuietException(); case 'v': verbose = true; break; case 'b': biasValue = (float)strtod( optarg, NULL ); if( biasValue<0.0f || biasValue>1.0f ) throw pfs::Exception("incorrect bias value, accepted range is (0..1)"); break; case '?': throw QuietException(); case ':': throw QuietException(); } } VERBOSE_STR << ": bias: " << biasValue << endl; while( true ) { pfs::Frame *frame = pfsio.readFrame( stdin ); if( frame == NULL ) break; // No more frames pfs::Channel *X, *Y, *Z; frame->getXYZChannels( X, Y, Z ); frame->getTags()->setString("LUMINANCE", "RELATIVE"); //--- if( Y == NULL ) throw pfs::Exception( "Missing X, Y, Z channels in the PFS stream" ); int w = Y->getCols(); int h = Y->getRows(); float maxLum,avLum; calculateLuminance( w, h, Y->getRawData(), avLum, maxLum ); VERBOSE_STR << ": maximum luminance: " << maxLum << endl; VERBOSE_STR << ": average luminance: " << avLum << endl; pfs::Array2DImpl* L = new pfs::Array2DImpl(w,h); tmo_drago03(w, h, Y->getRawData(), L->getRawData(), maxLum, avLum, biasValue); for( int y=0 ; y * * $Id: tmo_drago03.cpp,v 1.4 2008/11/04 23:43:08 rafm Exp $ */ #include #include #include #include "tmo_drago03.h" #include "pfstmo.h" #include /// Type of algorithm #define FAST 0 inline float biasFunc(float b, float x) { return pow(x, b); // pow(x, log(bias)/log(0.5) } //------------------------------------------- void calculateLuminance(unsigned int width, unsigned int height, const float* Y, float& avLum, float& maxLum ) { avLum = 0.0f; maxLum = 0.0f; int size = width * height; for( int i=0 ; i maxLum ) ? Y[i] : maxLum ; } avLum =exp( avLum/ size); } void tmo_drago03(unsigned int width, unsigned int height, const float* nY, float* nL, float maxLum, float avLum, float bias) { const float LOG05 = -0.693147f; // log(0.5) assert(nY!=NULL); assert(nL!=NULL); const pfstmo::Array2D* Y = new pfstmo::Array2D(width, height, const_cast(nY)); pfstmo::Array2D* L = new pfstmo::Array2D(width, height, nL); int nrows = Y->getRows(); // image size int ncols = Y->getCols(); assert(nrows==L->getRows() && ncols==L->getCols() ); maxLum /= avLum; // normalize maximum luminance by average luminance float divider = log10(maxLum+1.0f); float biasP = log(bias)/LOG05; #if !FAST // Normal tone mapping of every pixel for( int y=0 ; y-1.0f && average<1.0f ) { float interpol = log(2.0f + biasFunc(biasP, (*Y)(x+1,y+1)/maxLum) * 8.0f); for (i=0; i<3; i++) for (j=0; j<3; j++) { float Yw = (*Y)(x+i,y+j); if( Yw<1.0f ) { float L = Yw*(6.0f+Yw) / (6.0f+4.0f*Yw); Yw = (L/interpol) / divider; } else if( Yw>=1.0f && Yw<2.0f ) { float L = Yw*(6.0f+0.7662*Yw) / (5.9897f+3.7658f*Yw); Yw = (L/interpol) / divider; } else Yw = ( log(Yw+1.0f)/interpol ) / divider; (*L)(x+i,y+j) = Yw; } } else { for (i=0; i<3; i++) for (j=0; j<3; j++) { float Yw = (*Y)(x+i,y+j); float interpol = log(2.0f+biasFunc(biasP, Yw/maxLum)*8.0f); (*L)(x+i,y+j) = ( log(Yw+1.0f)/interpol ) / divider; } } } #endif // #else #ifndef FAST delete L; delete Y; } pfstools-2.2.0/src/tmo/drago03/pfstmo_drago03.10000664000701400070140000000201114105165615017612 0ustar rkm38rkm38.TH "pfstmo_drago03" 1 .SH NAME pfstmo_drago03 \- Adaptive logarithmic tone mapping operator .SH SYNOPSIS .B pfstmo_drago03 [--bias ] [--verbose] [--help] .SH DESCRIPTION This command implements adaptive logarithmic tone mapping operator as described in: Adaptive logarithmic mapping for displaying high contrast scenes. F. Drago, K. Myszkowski, T. Annen, and N. Chiba. In Eurographics 2003. According to the paper, results of this TMO require gamma correction. .SH OPTIONS .TP --bias , -b Bias value within the range 0..1. Default value: 0.85 .TP --verbose Print additional information during program execution. .TP --help Print list of commandline options. .SH EXAMPLES .TP pfsin memorial.hdr | pfstmo_drago03 -b 0.8 | pfsgamma -g 1.7 | pfsout memorial.png Tone map image, apply gamma correction, and save it in png format. .SH "SEE ALSO" .BR pfsgamma (1) .BR pfsin (1) .BR pfsout (1) .BR pfsview (1) .SH BUGS Please report bugs and comments on implementation to Grzegorz Krawczyk . pfstools-2.2.0/src/tmo/drago03/tmo_drago03.h0000664000701400070140000000406214105165615017200 0ustar rkm38rkm38/** * @brief Frederic Drago logmapping operator * * This file is a part of PFSTMO package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Grzegorz Krawczyk, * * $Id: tmo_drago03.h,v 1.3 2008/09/04 12:46:48 julians37 Exp $ */ #ifndef _tmo_drago03_h_ #define _tmo_drago03_h_ /** * @brief Frederic Drago Logmapping Algorithm * * Implementation obtained from source code provided * by Frederic Drago on 16 May 2003. * * @param width image width * @param height image height * @param Y [in] image luminance values * @param L [out] tone mapped values * @param maxLum maximum luminance in the image * @param avLum logarithmic average of luminance in the image * @param bias bias parameter of tone mapping algorithm (eg 0.85) */ void tmo_drago03(unsigned int width, unsigned int height, const float* Y, float* L, float maxLum, float avLum, float bias); /** * @brief Find average and maximum luminance in an image * * @param Y [in] image luminance values * @param avLum [out] average luminance * @param maxLum [out] maximum luminance */ void calculateLuminance(unsigned int width, unsigned int height, const float* Y, float& avLum, float& maxLum ); #endif pfstools-2.2.0/src/tmo/reinhard05/0002775000701400070140000000000014105165616015414 5ustar rkm38rkm38pfstools-2.2.0/src/tmo/reinhard05/tmo_reinhard05.h0000664000701400070140000000364714105165615020414 0ustar rkm38rkm38/** * @file tmo_reinhard05.cpp * @brief Tone map XYZ channels using Reinhard05 model * * Dynamic Range Reduction Inspired by Photoreceptor Physiology. * E. Reinhard and K. Devlin. * In IEEE Transactions on Visualization and Computer Graphics, 2005. * * This file is a part of PFSTMO package. * ---------------------------------------------------------------------- * Copyright (C) 2007 Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Grzegorz Krawczyk, * * $Id: tmo_reinhard05.h,v 1.2 2008/09/04 12:46:49 julians37 Exp $ */ #ifndef _tmo_reinhard05_h_ #define _tmo_reinhard05_h_ /** * @brief: Tone mapping algorithm [Reinhard2005] * * @param width image width * @param height image height * @param R red channel * @param G green channel * @param B blue channel * @param Y luminance channel * @param br brightness level -8:8 (def 0) * @param ca amount of chromatic adaptation 0:1 (saturation, def 0) * @param la amount of light adaptation 0:1 (local/global, def 1) */ void tmo_reinhard05(unsigned int width, unsigned int height, float* R, float* G, float* B, const float* Y, float br, float ca, float la ); #endif pfstools-2.2.0/src/tmo/reinhard05/CMakeLists.txt0000664000701400070140000000073714105165615020160 0ustar rkm38rkm38include_directories ("${PROJECT_BINARY_DIR}/" "${PROJECT_SOURCE_DIR}/src/pfs" "${PROJECT_SOURCE_DIR}/src/tmo/pfstmo") if( NOT HAS_GETOPT ) include_directories ("${GETOPT_INCLUDE}") endif( NOT HAS_GETOPT ) link_directories("${PROJECT_SOURCE_DIR}/src/pfs") set(TRG pfstmo_reinhard05) add_executable(${TRG} ${TRG}.cpp tmo_reinhard05.cpp "${GETOPT_OBJECT}") target_link_libraries(${TRG} pfs) install (TARGETS ${TRG} DESTINATION bin) install (FILES ${TRG}.1 DESTINATION ${MAN_DIR}) pfstools-2.2.0/src/tmo/reinhard05/pfstmo_reinhard05.cpp0000664000701400070140000001162214105165616021451 0ustar rkm38rkm38/** * @file pfstmo_reinhard05.cpp * @brief Tone map XYZ channels using Reinhard05 model * * Dynamic Range Reduction Inspired by Photoreceptor Physiology. * E. Reinhard and K. Devlin. * In IEEE Transactions on Visualization and Computer Graphics, 2005. * * * This file is a part of PFSTMO package. * ---------------------------------------------------------------------- * Copyright (C) 2007 Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Grzegorz Krawczyk, * * $Id: pfstmo_reinhard05.cpp,v 1.2 2008/09/04 12:46:49 julians37 Exp $ */ #include #include #include #include #include #include #include #include "tmo_reinhard05.h" using namespace std; #define PROG_NAME "pfstmo_reinhard05" /// verbose mode bool verbose = false; class QuietException { }; void printHelp() { fprintf( stderr, PROG_NAME " (" PACKAGE_STRING "): \n" "\t[--brightness ] [--saturation ] [--light ]\n" "\t[--verbose] [--help] \n" "See man page for more information.\n" ); } void pfstmo_reinhard05( int argc, char* argv[] ) { pfs::DOMIO pfsio; //--- default tone mapping parameters; float brightness = 0.0f; float chromaticadaptation = 0.5f; float lightadaptation = 0.75f; static struct option cmdLineOptions[] = { { "help", no_argument, NULL, 'h' }, { "verbose", no_argument, NULL, 'v' }, { "brightness", required_argument, NULL, 'b' }, { "chromatic", required_argument, NULL, 'c' }, { "light", required_argument, NULL, 'l' }, { NULL, 0, NULL, 0 } }; int optionIndex = 0; while( 1 ) { int c = getopt_long (argc, argv, "hvb:c:l:", cmdLineOptions, &optionIndex); if( c == -1 ) break; switch( c ) { case 'h': printHelp(); throw QuietException(); case 'v': verbose = true; break; case 'b': brightness = (float)strtod( optarg, NULL ); if( brightness<-8.0f || brightness>8.0f ) throw pfs::Exception("brightness value out of range, should be <-8..8>"); break; case 'c': chromaticadaptation = (float)strtod( optarg, NULL ); if( chromaticadaptation<0.0f || chromaticadaptation>1.0f ) throw pfs::Exception("chromatic adaptation value out of range, should be <0..1>"); break; case 'l': lightadaptation = (float)strtod( optarg, NULL ); if( lightadaptation<0.0f || lightadaptation>1.0f ) throw pfs::Exception("light adaptation value out of range, should be <0..1>"); break; case '?': throw QuietException(); case ':': throw QuietException(); } } VERBOSE_STR << "brightness: " << brightness << endl; VERBOSE_STR << "chromatic adaptation: " << chromaticadaptation << endl; VERBOSE_STR << "light adaptation: " << lightadaptation << endl; while( true ) { pfs::Frame *frame = pfsio.readFrame( stdin ); if( frame == NULL ) break; // No more frames pfs::Channel *X, *Y, *Z; frame->getXYZChannels( X, Y, Z ); frame->getTags()->setString("LUMINANCE", "RELATIVE"); //--- if( Y==NULL || X==NULL || Z==NULL) throw pfs::Exception( "Missing X, Y, Z channels in the PFS stream" ); // tone mapping int w = Y->getCols(); int h = Y->getRows(); pfs::Array2DImpl* R = new pfs::Array2DImpl(w,h); pfs::Array2DImpl* G = new pfs::Array2DImpl(w,h); pfs::Array2DImpl* B = new pfs::Array2DImpl(w,h); pfs::transformColorSpace( pfs::CS_XYZ, X, Y, Z, pfs::CS_RGB, R, G, B ); tmo_reinhard05(w, h, R->getRawData(), G->getRawData(), B->getRawData(), Y->getRawData(), brightness, chromaticadaptation, lightadaptation ); pfs::transformColorSpace( pfs::CS_SRGB, R, G, B, pfs::CS_XYZ, X, Y, Z ); delete R; delete G; delete B; //--- pfsio.writeFrame( frame, stdout ); pfsio.freeFrame( frame ); } } int main( int argc, char* argv[] ) { try { pfstmo_reinhard05( argc, argv ); } catch( pfs::Exception ex ) { fprintf( stderr, PROG_NAME " error: %s\n", ex.getMessage() ); return EXIT_FAILURE; } catch( QuietException ex ) { return EXIT_FAILURE; } return EXIT_SUCCESS; } pfstools-2.2.0/src/tmo/reinhard05/tmo_reinhard05.cpp0000664000701400070140000001026114105165616020736 0ustar rkm38rkm38/** * @file tmo_reinhard05.cpp * @brief Tone map XYZ channels using Reinhard05 model * * Dynamic Range Reduction Inspired by Photoreceptor Physiology. * E. Reinhard and K. Devlin. * In IEEE Transactions on Visualization and Computer Graphics, 2005. * * * This file is a part of PFSTMO package. * ---------------------------------------------------------------------- * Copyright (C) 2007 Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Grzegorz Krawczyk, * * $Id: tmo_reinhard05.cpp,v 1.4 2009/04/15 11:49:32 julians37 Exp $ */ #include #include #include #include "tmo_reinhard05.h" #include "pfstmo.h" #include void tmo_reinhard05(unsigned int width, unsigned int height, float* nR, float* nG, float* nB, const float* nY, float br, float ca, float la ) { const pfstmo::Array2D* Y = new pfstmo::Array2D(width, height, const_cast(nY)); pfstmo::Array2D* R = new pfstmo::Array2D(width, height, nR); pfstmo::Array2D* G = new pfstmo::Array2D(width, height, nG); pfstmo::Array2D* B = new pfstmo::Array2D(width, height, nB); float max_lum = (*Y)(0); float min_lum = (*Y)(0); float world_lum = 0.0; float Cav[] = { 0.0f, 0.0f, 0.0f}; float Lav = 0.0f; int im_width = Y->getCols(); int im_height = Y->getRows(); int im_size = im_width * im_height; for( int i=1 ; i lum) ? max_lum : lum; min_lum = (min_lum < lum) ? min_lum : lum; world_lum += log(2.3e-5+lum); Cav[0] += (*R)(i); Cav[1] += (*G)(i); Cav[2] += (*B)(i); Lav += lum; } world_lum /= im_size; Cav[0] /= im_size; Cav[1] /= im_size; Cav[2] /= im_size; Lav /= im_size; //--- tone map image max_lum = log( max_lum ); min_lum = log( min_lum ); // image key float k = (max_lum - world_lum) / (max_lum - min_lum); // image contrast based on key value float m = 0.3f+0.7f*pow(k,1.4f); // image brightness float f = exp(-br); float max_col = 0.0f; float min_col = 1.0f; int x,y; for( y=0 ; ymax_col) ? col : max_col; min_col = (col] [--chromatic ] [--light ] [--verbose] [--help] .SH DESCRIPTION This command implements a tone mapping operator as described in: Dynamic Range Reduction Inspired by Photoreceptor Physiology. E. Reinhard and K. Devlin. In IEEE Transactions on Visualization and Computer Graphics, 2005. .SH OPTIONS .TP --brightness , -b Brightness correction (-8..8), parameter 'f' in paper. Default value: 0.0 .TP --chromatic , -c Amount of chromatic adaptation (von Kries model) (0..1), parameter 'c' in paper. Default value: 0.0 .TP --light , -l Amount of light adaptation adaptation (0..1), parameter 'l' in paper. Default value: 1.0 .TP --verbose Print additional information during program execution. .TP --help Print list of commandline options. .SH EXAMPLES .TP pfsin memorial.hdr | pfstmo_reinhard05 -s 0.5 | pfsgamma -g 2.2 | pfsout memorial.png Tone map image and save it in png format. .SH "SEE ALSO" .BR pfsgamma (1) .BR pfsin (1) .BR pfsout (1) .BR pfsview (1) .SH BUGS Please report bugs and comments on implementation to Grzegorz Krawczyk . pfstools-2.2.0/src/tmo/fattal02/0002775000701400070140000000000014105165616015070 5ustar rkm38rkm38pfstools-2.2.0/src/tmo/fattal02/tmo_fattal02.h0000664000701400070140000000450214105165616017534 0ustar rkm38rkm38/** * @file tmo_fattal02.h * @brief TMO: Gradient Domain High Dynamic Range Compression (header) * * Implementation of Gradient Domain High Dynamic Range Compression * by Raanan Fattal, Dani Lischinski, Michael Werman. * * * This file is a part of PFSTMO package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Grzegorz Krawczyk, * * $Id: tmo_fattal02.h,v 1.4 2012/03/31 16:49:49 tk255 Exp $ */ #ifndef _tmo_fattal02_h_ #define _tmo_fattal02_h_ /** * @brief Gradient Domain High Dynamic Range Compression * * Implementation of Gradient Domain High Dynamic Range Compression * by Raanan Fattal, Dani Lischinski, Michael Werman. * * @param width image width * @param height image height * @param Y [in] image luminance values * @param L [out] tone mapped values * @param alfa parameter alfa (refer to the paper) * @param beta parameter beta (refer to the paper) * @param noise gradient level of noise (extra parameter) * @param cut_min percentile cutoff luminosity to be excluded from final image * @param cut_max percentile cutoff luminosity to be excluded from final image * @param fftsolver whether to use the fft-solver instead of the multi-grid */ void tmo_fattal02(unsigned int width, unsigned int height, const float* nY, float* nL, float alfa, float beta, float gamma, float noise, int detail_level, float black_point, float white_point, bool fftsolver); #endif pfstools-2.2.0/src/tmo/fattal02/pfstmo_fattal02.10000664000701400070140000001440314105165616020157 0ustar rkm38rkm38.TH "pfstmo_fattal02" 1 .SH NAME pfstmo_fattal02 \- Gradient domain high dynamic range compression .SH SYNOPSIS .B pfstmo_fattal02 [--alpha ] [--beta ] [--gamma ] [--saturation ] [--noise ] [--detail-level ] [--black-point ] [--white-point ] [--multigrid] [--verbose] [--help] .SH DESCRIPTION This command implements a tone mapping operator as described in: Gradient Domain High Dynamic Range Compression R. Fattal, D. Lischinski, and M. Werman In ACM Transactions on Graphics, 31(3), p. 249, 2002. With respect to the original paper, this program provides additional parameter which limits the amplification of noise. The noise is often starkly amplified because of division by zero in one of the equations in the paper. Extension contributed by Przemyslaw Bazarnik. At the core of the programme is a Poisson PDE which as suggested in the original paper is solved using a Full Multigrid Algorithm. However, this is an iterative solver which seems to lose accuracy when applied to higher resolution images resulting in halo effects and surreal looking images. For that reason a second solver has been implemented using the discrete cosine transform as the underlying method and is considerably more accurate mainly because it is a direct solver. This solver is the preferred method and is used by default. The old multigrid solver can be selected with the --multigrid (-m) option. .SH OPTIONS .TP --alpha , -a Set alpha parameter. This parameter is depreciated as setting a other than 1.0 has only the effect of a global gamma adjustment of the luminance channel which can be directly specified using the --gamma option. See the paper for the definition of alpha. It can be shown, although not mentioned in the paper, that setting alpha other than 1.0 has the same effect as setting gamma = alpha^(k*(1-beta)), where beta is the value as specified by --beta and k is the number of levels of the Gaussian Pyramid (see paper for details), which depends on the image pixel size (smallest k so that 2^(k+detail_level) >= min(width,height)/MSIZE, MSIZE see source code, e.g. 8 or 32). .TP --beta , -b Set beta parameter. sets the strength of gradient (local contrast) modification. Suggested range is 0.8 to 0.96, default is 0.9 (see paper for details). Value of 1 does not change contrasts, values above 1 reverse the effect: local contrast is stretched and details are attenuated. Values below 0.5 lead to very strong amplification of small contrast, so consider using --noise parameter to prevent noise. .TP --gamma , -g Set luminance gamma adjustment. This can be described as a global contrast enhancement and is applied after the local enhancement as specified by the parameter --beta is performed. Gamma adjustment or correction is defined by a power-law, in this case L_out(x,y) = L_in(x,y)^gamma, where L_in(x,y)=exp(I(x,y)) is the luminance value after the local contrast enhancement (I is the solution of the Poisson PDE). The suggested range for is 0.6 to 1.0, default is 0.8. .TP --saturation , -s Amount of color saturation. Suggested range is 0.4 to 0.8. Default value: 0.8. .TP --noise , -n Reduces the gradient amplification value for gradients close to 0 and reduces noise as a result. defines gradient value (luminance difference of adjacent pixels) which is treated as noise. Suggested range is 0.0 to the value of alpha. Default value calculated based on alpha: 0.001*alpha. .TP --detail-level , -d Specifies up to which detail level the local contrast enhancement should be performed. It basically means that local contrast levels within small squares of pixel size 2^ are not changed. In the implementation this corresponds to removing the finest levels of the Gaussian Pyramid as described in the paper, i.e. the paper only considers =0. Suggested values are 1, 2 or 3; 3 for high resolution images. The default is 3 for --fftsolver, and 0 if the original multi-level solver is used (to be consistent with the paper). .TP --white-point , -w Specifies the percentage of pixels which are allowed to be overexposed and therefore blown out. This can be useful for example when there is a very bright object in the image like the sun and details of it do not need to be resolved. As a result the overall image will look brighter the greater is. Default is 0.5. .TP --black-point , -k Same as --white-point but for under-exposed pixels. Default is 0.1. .TP --multigrid, -m Enable the use of the multigrid solver as suggested by the original paper. For accuracy the default fft solver is generally recommended especially when using high resolution images. The user will benefit by obtaining photo-realistic rather than surreal looking images. The fft solver is also faster despite the fact it is only O(n*log n) with n=width*height, as compared to O(n) for the multigrid solver. The speed improvement is thanks to the very efficient fftw3 library which is used to calculate the discrete cosine transform. .TP --verbose Print additional information during program execution. .TP --help Print list of command line options. .SH EXAMPLES .TP pfsin memorial.hdr | pfstmo_fattal02 -v -t | pfsout memorial.png Tone map image (using fft solver) and save it in png format. .TP pfsin memorial.hdr | pfstmo_fattal02 -v -t -b 0.85 -g 0.7 -w 2.0 \\ | pfsout memorial.png Tone map image (using fft solver) with stronger contrast modification than default, i.e. beta=0.85, gamma=0.7 and white point 2.0%. .TP pfsin memorial.hdr | pfstmo_fattal02 -v | pfsout memorial.png Tone map image (old style) and save it in png format. .SH "SEE ALSO" .BR pfsin (1) .BR pfsout (1) .BR pfsview (1) .SH KNOWN ISSUES For stronger local contrast enhancements (beta<0.9) the fft solver (--fftsolver) might produce slightly dark image corners. This can be mitigated using bigger values for the --noise parameter. With a value of --detail-level greater than 0, the internal implementation could be made much more efficient as only a reduced sized PDE would need to be solved, greatly improving speed. .SH BUGS Please report bugs and comments on implementation to the pfstools discussion group (http://groups.google.com/group/pfstools). For bugs specific to the FFT solver email Tino Kluge . pfstools-2.2.0/src/tmo/fattal02/CMakeLists.txt0000664000701400070140000000152314105165616017627 0ustar rkm38rkm38include_directories ("${PROJECT_BINARY_DIR}/" "${PROJECT_SOURCE_DIR}/src/pfs" "${PROJECT_SOURCE_DIR}/src/tmo/pfstmo") if( NOT HAS_GETOPT ) include_directories ("${GETOPT_INCLUDE}") endif( NOT HAS_GETOPT ) link_directories("${PROJECT_SOURCE_DIR}/src/pfs") if( FFTW_FOUND AND OPENMP_FOUND) set( FFTW_LIBRARIES ${FFTW_LIBS} ) set( PDE_FFT "pde_fft.cpp" ) set( CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}" ) set( CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${OpenMP_C_FLAGS}" ) else( FFTW_FOUND AND OPENMP_FOUND ) set( FFTW_LIBRARIES ) endif( FFTW_FOUND AND OPENMP_FOUND ) set(TRG pfstmo_fattal02) add_executable(${TRG} ${TRG}.cpp tmo_fattal02.cpp pde.cpp ${PDE_FFT} "${GETOPT_OBJECT}") target_link_libraries(${TRG} pfs ${FFTW_LIBRARIES}) install (TARGETS ${TRG} DESTINATION bin) install (FILES ${TRG}.1 DESTINATION ${MAN_DIR}) pfstools-2.2.0/src/tmo/fattal02/tmo_fattal02.cpp0000664000701400070140000003333414105165616020074 0ustar rkm38rkm38/** * @file tmo_fattal02.cpp * @brief TMO: Gradient Domain High Dynamic Range Compression * * Implementation of Gradient Domain High Dynamic Range Compression * by Raanan Fattal, Dani Lischinski, Michael Werman. * * @author Grzegorz Krawczyk, * * * This file is a part of PFSTMO package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * $Id: tmo_fattal02.cpp,v 1.10 2012/06/22 10:59:15 rafm Exp $ */ #include #include #include #include #include #include #include #include "pfstmo.h" #include "pde.h" using namespace std; #if !defined(HAVE_FFTW3) || !defined(HAVE_OpenMP) // Dummy function, compiled when FFTW3 not available void solve_pde_fft(pfstmo::Array2D *F, pfstmo::Array2D *U, bool adjust_bound) { throw pfs::Exception("FFT solver not available. Compile with libfftw3."); } #endif //for debugging purposes #if 0 #define PFSEOL "\x0a" static void dumpPFS( const char *fileName, const pfstmo::Array2D *data, const char *channelName ) { FILE *fh = fopen( fileName, "wb" ); assert( fh != NULL ); int width = data->getCols(); int height = data->getRows(); fprintf( fh, "PFS1" PFSEOL "%d %d" PFSEOL "1" PFSEOL "0" PFSEOL "%s" PFSEOL "0" PFSEOL "ENDH", width, height, channelName ); for( int y = 0; y < height; y++ ) for( int x = 0; x < width; x++ ) { float d = (*data)(x,y); fwrite( &d, sizeof( float ), 1, fh ); } fclose( fh ); } #endif //-------------------------------------------------------------------- void downSample(pfstmo::Array2D* A, pfstmo::Array2D* B) { int width = B->getCols(); int height = B->getRows(); int x,y; for( y=0 ; ygetCols(); int height = I->getRows(); int size = width*height; int x,y; pfstmo::Array2D* T = new pfstmo::Array2D(width,height); //--- X blur for( y=0 ; ygetCols(); int height = H->getRows(); int size = width*height; pyramids[0] = new pfstmo::Array2D(width,height); for( int i=0 ; igetCols(); int height = H->getRows(); float divider = pow( 2.0f, k+1 ); float avgGrad = 0.0f; for( int y=0 ; ygetCols(); int height = B->getRows(); int awidth = A->getCols(); int aheight = A->getRows(); int x,y; for( y=0 ; ygetCols(); // int height = A->getRows(); // int x,y; // for( y=0 ; ygetCols(); int height = gradients[nlevels-1]->getRows(); int k; pfstmo::Array2D** fi = new pfstmo::Array2D*[nlevels]; fi[nlevels-1] = new pfstmo::Array2D(width,height); for( k=0 ; k=0 ; k-- ) { width = gradients[k]->getCols(); height = gradients[k]->getRows(); // only apply gradients to levels>=detail_level but at least to the coarsest if(k>=detail_level || k==nlevels-1) { DEBUG_STR << "calculateFiMatrix: apply gradient to level " << k << endl; for( int y=0 ; y1e-4 ) value = a/(grad+noise) * pow((grad+noise)/a, beta); (*fi[k])(x,y) *= value; } } // create next level if( k>1 ) { width = gradients[k-1]->getCols(); height = gradients[k-1]->getRows(); fi[k-1] = new pfstmo::Array2D(width,height); } else fi[0] = FI; // highest level -> result if( k>0 ) { upSample(fi[k], fi[k-1]); // upsample to next level gaussianBlur(fi[k-1],fi[k-1]); } } for( k=1 ; kgetRows() * I->getCols(); std::vector vI; for( int i=0 ; i(nY)); pfstmo::Array2D* L = new pfstmo::Array2D(width, height, nL); int MSIZE=32; // minimum size of gaussian pyramid (32 as in paper) // I believe a smaller value than 32 results in slightly better overall // quality but I'm only applying this if the newly implemented fft solver // is used in order not to change behaviour of the old version // TODO: best let the user decide this value if(fftsolver) MSIZE=8; int size = width*height; int x,y,i,k; // find max & min values, normalize to range 0..100 and take logarithm float minLum = (*Y)(0,0); float maxLum = (*Y)(0,0); for( i=0 ; imaxLum ) ? (*Y)(i) : maxLum; } pfstmo::Array2D* H = new pfstmo::Array2D(width, height); for( i=0 ; i=MSIZE ) { nlevels++; mins /= 2; } pfstmo::Array2D** pyramids = new pfstmo::Array2D*[nlevels]; createGaussianPyramids(H, pyramids, nlevels); // calculate gradients and its average values on pyramid levels pfstmo::Array2D** gradients = new pfstmo::Array2D*[nlevels]; float* avgGrad = new float[nlevels]; for( k=0 ; kgetCols(), pyramids[k]->getRows()); avgGrad[k] = calculateGradients(pyramids[k],gradients[k], k); } // calculate fi matrix pfstmo::Array2D* FI = new pfstmo::Array2D(width, height); calculateFiMatrix(FI, gradients, avgGrad, nlevels, detail_level, alfa, beta, noise); // dumpPFS( "FI.pfs", FI, "Y" ); // attenuate gradients pfstmo::Array2D* Gx = new pfstmo::Array2D(width, height); pfstmo::Array2D* Gy = new pfstmo::Array2D(width, height); // the fft solver solves the Poisson pde but with slightly different // boundary conditions, so we need to adjust the assembly of the right hand // side accordingly (basically fft solver assumes U(-1) = U(1), whereas zero // Neumann conditions assume U(-1)=U(0)), see also divergence calculation if(fftsolver) for( y=0 ; y<(int)height ; y++ ) for( x=0 ; x<(int)width ; x++ ) { // sets index+1 based on the boundary assumption H(N+1)=H(N-1) int yp1 = (y+1 >= (int)height ? height-2 : y+1); int xp1 = (x+1 >= (int)width ? width-2 : x+1); // forward differences in H, so need to use between-points approx of FI (*Gx)(x,y) = ((*H)(xp1,y)-(*H)(x,y)) * 0.5*((*FI)(xp1,y)+(*FI)(x,y)); (*Gy)(x,y) = ((*H)(x,yp1)-(*H)(x,y)) * 0.5*((*FI)(x,yp1)+(*FI)(x,y)); } else for( y=0 ; y 0 ) (*DivG)(x,y) -= (*Gx)(x-1,y); if( y > 0 ) (*DivG)(x,y) -= (*Gy)(x,y-1); if(fftsolver) { if(x==0) (*DivG)(x,y) += (*Gx)(x,y); if(y==0) (*DivG)(x,y) += (*Gy)(x,y); } } // dumpPFS( "DivG.pfs", DivG, "Y" ); DEBUG_STR << "tmo_fattal02: recovering image" << endl; // solve pde and exponentiate (ie recover compressed image) pfstmo::Array2D* U = new pfstmo::Array2D(width, height); if(fftsolver) { solve_pde_fft( DivG, U ); } else { // solve_pde_sor( DivG, U ); solve_pde_multigrid( DivG, U ); } DEBUG_STR << "pde residual error: " << residual_pde(U, DivG) << std::endl; for( y=0 ; y=0.0f && (cut_max<=1.0f) && (cut_min 1.0 } // clean up DEBUG_STR << "tmo_fattal02: clean up" << endl; delete H; for( i=0 ; i U(i-1) - 2 U(i) + U(i+1) becomes // i=0: U(0) - 2 U(0) + U(1) = -U(0) + U(1) // - our system: assume U(-1)=U(1) --> this becomes // i=0: U(1) - 2(0) + U(1) = -2 U(0) + 2 U(1) // // The multi grid solver solve_pde_multigrid() solves the 2d Poisson pde // with the right Neumann boundary conditions, U(-1)=U(0), see function // atimes(). This means the assembly of the right hand side F is different // for both solvers. #include #include #include #include #include #include #include #include #include #include "pde.h" using namespace std; #ifndef SQR #define SQR(x) (x)*(x) #endif // returns T = EVy A EVx^tr // note, modifies input data void transform_ev2normal(pfstmo::Array2Dd *A, pfstmo::Array2Dd *T) { int width = A->getCols(); int height = A->getRows(); assert((int)T->getCols()==width && (int)T->getRows()==height); // the discrete cosine transform is not exactly the transform needed // need to scale input values to get the right transformation for(int y=1 ; ygetRawData(), T->getRawData(), FFTW_REDFT00, FFTW_REDFT00, FFTW_ESTIMATE); fftw_execute(p); fftw_destroy_plan(p); } // returns T = EVy^-1 * A * (EVx^-1)^tr void transform_normal2ev(pfstmo::Array2Dd *A, pfstmo::Array2Dd *T) { int width = A->getCols(); int height = A->getRows(); assert((int)T->getCols()==width && (int)T->getRows()==height); // executes 2d discrete cosine transform fftw_plan p; p=fftw_plan_r2r_2d(height, width, A->getRawData(), T->getRawData(), FFTW_REDFT00, FFTW_REDFT00, FFTW_ESTIMATE); fftw_execute(p); fftw_destroy_plan(p); // need to scale the output matrix to get the right transform for(int y=0 ; y get_lambda(int n) { assert(n>1); std::vector v(n); for(int i=0; igetCols(); int height = F->getRows(); double sum=0.0; for(int y=1 ; ygetCols(); int height = F->getRows(); assert((int)U->getCols()==width && (int)U->getRows()==height); // activate parallel execution of fft routines fftw_init_threads(); fftw_plan_with_nthreads(omp_get_max_threads()); // in general there might not be a solution to the Poisson pde // with Neumann boundary conditions unless the boundary satisfies // an integral condition, this function modifies the boundary so that // the condition is exactly satisfied if(adjust_bound) { DEBUG_STR << "solve_pde_fft: checking boundary conditions" << std::endl; make_compatible_boundary(F); } // transforms F into eigenvector space: Ftr = DEBUG_STR << "solve_pde_fft: transform F to ev space (fft)" << std::endl; pfstmo::Array2Dd* F_tr = new pfstmo::Array2Dd(width,height); transform_normal2ev(F, F_tr); F->allocate(1,1); // no longer needed so release memory (better not?) DEBUG_STR << "solve_pde_fft: F_tr(0,0) = " << (*F_tr)(0,0); DEBUG_STR << " (must be 0 for solution to exist)" << std::endl; // in the eigenvector space the solution is very simple DEBUG_STR << "solve_pde_fft: solve in eigenvector space" << std::endl; pfstmo::Array2Dd* U_tr = new pfstmo::Array2Dd(width,height); std::vector l1=get_lambda(height); std::vector l2=get_lambda(width); for(int y=0 ; ygetCols(); int height = F->getRows(); assert((int)U->getCols()==width && (int)U->getRows()==height); pfstmo::Array2Dd* Fd = new pfstmo::Array2Dd(width,height); pfstmo::Array2Dd* Ud = new pfstmo::Array2Dd(width,height); // convert float array to double array for(int i=0; igetCols(); int height = U->getRows(); assert((int)F->getCols()==width && (int)F->getRows()==height); double res=0.0; for(int y=1;y void laplace_fft(pfstmo::Array2DBase* U, pfstmo::Array2DBase* F) { int width = U->getCols(); int height = U->getRows(); assert((int) F->getCols()==width && (int)F->getRows()==height); for(int y=0;y * @author Rafal Mantiuk, * * Some code from Numerical Recipes in C * * * This file is a part of PFSTMO package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * $Id: pde.cpp,v 1.8 2012/05/06 15:10:34 tk255 Exp $ */ #if HAVE_CONFIG_H #include #endif #include #include #include #include //#include #include #include #include "pde.h" #include using namespace std; ////////////////////////////////////////////////////////////////////// // tune the multi-level solver #define MODYF 0 /* 1 or 0 (1 is better) */ #define MINS 16 /* minimum size 4 6 or 100 */ //#define MODYF_SQRT -1.0f /* -1 or 0 */ #define SMOOTH_IT 1 // orig 1, (minimum 1) #define BCG_STEPS 20 // orig 20 #define BCG_TOL 1e-3 // orig 1e-3 #define V_CYCLE 2 // orig 2 // post improvement of the solution using additional cg-iterations #define BCG_POST_IMPROVE false #define BCG_POST_STEPS 2000 // very slow if > 100, only use on small image #define BCG_POST_TOL 1e-7 // precision #define EPS 1.0e-12 void linbcg(unsigned long n, float b[], float x[], int itol, float tol, int itmax, int *iter, float *err); inline float max( float a, float b ) { return a > b ? a : b; } inline float min( float a, float b ) { return a < b ? a : b; } //!! TODO: for debugging purposes // #define PFSEOL "\x0a" // static void dumpPFS( const char *fileName, const pfstmo::Array2D *data, const char *channelName ) // { // FILE *fh = fopen( fileName, "wb" ); // assert( fh != NULL ); // int width = data->getCols(); // int height = data->getRows(); // fprintf( fh, "PFS1" PFSEOL "%d %d" PFSEOL "1" PFSEOL "0" PFSEOL // "%s" PFSEOL "0" PFSEOL "ENDH", width, height, channelName ); // for( int y = 0; y < height; y++ ) // for( int x = 0; x < width; x++ ) { // fwrite( &((*data)(x,y)), sizeof( float ), 1, fh ); // } // fclose( fh ); // } ////////////////////////////////////////////////////////////////////// // Full Multigrid Algorithm for solving partial differential equations ////////////////////////////////////////////////////////////////////// void restrict( const pfstmo::Array2D *in, pfstmo::Array2D *out ) { const float inRows = in->getRows(); const float inCols = in->getCols(); const int outRows = out->getRows(); const int outCols = out->getCols(); const float dx = (float)in->getCols() / (float)out->getCols(); const float dy = (float)in->getRows() / (float)out->getRows(); const float filterSize = 0.5; float sx, sy; int x, y; for( y = 0, sy = dy/2-0.5; y < outRows; y++, sy += dy ) for( x = 0, sx = dx/2-0.5; x < outCols; x++, sx += dx ) { float pixVal = 0; float w = 0; for( float ix = max( 0, ceilf( sx-dx*filterSize ) ); ix <= min( floorf( sx+dx*filterSize ), inCols-1 ); ix++ ) for( float iy = max( 0, ceilf( sy-dx*filterSize ) ); iy <= min( floorf( sy+dx*filterSize), inRows-1 ); iy++ ) { pixVal += (*in)( (int)ix, (int)iy ); w += 1; } (*out)(x,y) = pixVal/w; } } // from_level>to_level, from_size>to_size // void restrict( pfstmo::Array2D *F, pfstmo::Array2D *T ) // { // // DEBUG_STR << "restrict" << endl; // int sxt = T->getCols(); // int syt = T->getRows(); // int sxf = F->getCols(); // int syf = F->getRows(); // int x,y; // float w[] = {.25, .5, .25}; // for( int x=0 ; x=0 && yf>=0 && xfgetCols() / (float)out->getCols(); float dy = (float)in->getRows() / (float)out->getRows(); float pad; float filterSamplingX = max( modff( dx, &pad ), 0.01f ); float filterSamplingY = max( modff( dy, &pad ), 0.01f ); const int outRows = out->getRows(); const int outCols = out->getCols(); const float inRows = in->getRows(); const float inCols = in->getCols(); const float filterSize = 1; float sx, sy; int x, y; for( y = 0, sy = -dy/2; y < outRows; y++, sy += dy ) for( x = 0, sx = -dx/2; x < outCols; x++, sx += dx ) { float pixVal = 0; float weight = 0; for( float ix = max( 0, ceilf( sx-filterSize ) ); ix <= min( floorf(sx+filterSize), inCols-1 ); ix++ ) for( float iy = max( 0, ceilf( sy-filterSize ) ); iy <= min( floorf( sy+filterSize), inRows-1 ); iy++ ) { float fx = fabs( sx - ix ); float fy = fabs( sy - iy ); const float fval = (1-fx)*(1-fy); pixVal += (*in)( (int)ix, (int)iy ) * fval; weight += fval; } assert( weight != 0 ); (*out)(x,y) = pixVal / weight; } } // to_levelgetCols(); int syt = T->getRows(); int sxf = F->getCols(); int syf = F->getRows(); int x,y; // elements that are copies for( x=0 ; x=sxt ) xp1 = xm1; (*T)(x,y) = ( (*T)(xm1,y) + (*T)(xp1,y) ) * 0.5f; } // even rows interpolated vertically (every column) for( x=0 ; x=syt ) yp1 = ym1; (*T)(x,y) = ( (*T)(x,ym1) + (*T)(x,yp1) ) * 0.5f; } } void exact_sollution( pfstmo::Array2D *F, pfstmo::Array2D *U ) { // DEBUG_STR << "exact sollution" << endl; int sx = F->getCols(); int sy = F->getRows(); int x,y; float h = 1.0/sqrt(sx*sy*1.0f); float h2 = h*h; setArray( U, 0.0f); return; /* also works well?? */ // if( sx==3 && sy==3 ) // { // (*U)(1,1) = -h2* (*F)(1,1) / 4.0f; // // boundary points // for( x=0 ; xgetRows(); cols = U->getCols(); const int n = rows*cols; int iter; float err; linbcg( n, F->getRawData()-1, U->getRawData()-1, 1, BCG_TOL, BCG_STEPS, &iter, &err); // fprintf( stderr, "." ); // Gauss relaxation is too slow // int sx = F->getCols(); // int sy = F->getRows(); // int x,y,i; // int shx; shift x // float h = 1.0f/sqrt(sx*sy*1.0f); // float h2 = h*h; // h2 = 1; // for( int pass=0 ; pass<2 ; pass++ ) // { // shx=pass; // for( y=0; ygetCols(); int sy = F->getRows(); float h = 1.0f/sqrt(sx*sy*1.0f); float h2i = 1.0/(h*h); h2i = 1; for( int y=0 ; ygetCols(); int sy = C->getRows(); for( int i=0 ; igetCols(); int ymax = F->getRows(); int i; // index for simple loops int k; // index for iterating through levels int k2; // index for iterating through levels in V-cycles int x,y; // 1. restrict f to coarse-grid (by the way count the number of levels) // k=0: fine-grid = f // k=levels: coarsest-grid int levels = 0; int mins = (xmax=MINS ) { levels++; mins = mins/2+MODYF; } // given function f restricted on levels pfstmo::Array2D** RHS = new pfstmo::Array2D*[levels+1]; // approximate initial sollutions on levels pfstmo::Array2D** IU = new pfstmo::Array2D*[levels+1]; // target functions in cycles (approximate sollution error (uh - ~uh) ) pfstmo::Array2D** VF = new pfstmo::Array2D*[levels+1]; VF[0] = new pfstmo::Array2D(xmax,ymax); RHS[0] = F; IU[0] = new pfstmo::Array2D(xmax,ymax); pfstmo::copyArray( U, IU[0] ); int sx=xmax; int sy=ymax; DEBUG_STR << "FMG: #0 size " << sx << "x" << sy << endl; for( k=0 ; k=0 ; k-- ) { // 4. interpolate sollution from last coarse-grid to finer-grid // interpolate from level k+1 to level k (finer-grid) prolongate( IU[k+1], IU[k] ); // 4.1. first target function is the equation target function // (following target functions are the defect) copyArray( RHS[k], VF[k] ); // 5. V-cycle (twice repeated) for( int cycle=0 ; cyclegetCols(), IU[k2]->getRows()); calculate_defect( D, IU[k2], VF[k2] ); // 9. restrict deffect as target function for next coarser-grid // def -> f[k2+1] restrict( D, VF[k2+1] ); delete D; } // 10. solve on coarsest-grid (target function is the deffect) // iu[levels] should contain sollution for // the f[levels] - last deffect, iu will now be the correction exact_sollution(VF[levels], IU[levels] ); // 11. upward stroke of V for( k2=levels-1 ; k2>=k ; k2-- ) { // 12. interpolate correction from last coarser-grid to finer-grid // iu[k2+1] -> cor pfstmo::Array2D* C = new pfstmo::Array2D(IU[k2]->getCols(), IU[k2]->getRows()); prolongate( IU[k2+1], C ); // 13. add interpolated correction to initial sollution at level k2 add_correction( IU[k2], C ); delete C; // fprintf( stderr, "Level: %d --------\n", k2 ); // 14. post-smoothing of current sollution using target function for( i=0 ; igetCols(); int ymax = F->getRows(); float rjac = 1.0 - 6.28/((xmax>ymax) ? xmax : ymax); int ipass, j, jsw, l, lsw, n; float anorm, anormf = 0.0, omega = 1.0, resid; // Compute initial norm of residual and terminate iteration when // norm has been reduced by a factor EPS. for (j = 0; j < xmax; j++) for (l = 0; l < ymax; l++) { anormf += fabs( (*F)(j,l) ); (*U)(j,l)=0.0f; } // Assumes initial u is zero. for (n = 1; n <= maxits; n++) { anorm = 0.0; jsw = 0; for (ipass = 1; ipass <= 2; ipass++) { // Odd - even ordering. lsw = jsw; for (j = 0; j < xmax; j++) { // j=xmax ) jp1=jm1; if( jm1<0 ) jm1=jp1; if( lp1>=ymax ) lp1=lm1; if( lm1<0 ) lm1=lp1; resid = (*U)(jp1,l) + (*U)(jm1,l) + (*U)(j,lp1) + (*U)(j,lm1) - 4.0* (*U)(j,l) - (*F)(j,l); anorm += fabs(resid); (*U)(j,l) -= omega * resid / -4.0; } lsw = 1 - lsw; } jsw = 1 - jsw; omega = ( n==1 && ipass==1 ? 1.0 / (1.0 - 0.5 * rjac * rjac) : 1.0 / (1.0 - 0.25 * rjac * rjac * omega)); } if( !(n%100) || n==1) DEBUG_STR << "SOR:> " << n << "\tAnorm: " << anorm << "\n"; if (anorm < EPS * anormf ) { DEBUG_STR << "SOR:> solved.\n"; return; } } DEBUG_STR << "SOR:> MAXITS exceeded\n"; } //#define EPS 1.0e-14 void asolve(unsigned long n, float b[], float x[], int itrnsp) { for( int r = 0; r < rows; r++ ) for( int c = 0; c < cols; c++ ) { x[idx(r,c)] = -4 * b[idx(r,c)]; } } void atimes(unsigned long n, float x[], float res[], int itrnsp) { for( int r = 1; r < rows-1; r++ ) for( int c = 1; c < cols-1; c++ ) { res[idx(r,c)] = x[idx(r-1,c)] + x[idx(r+1,c)] + x[idx(r,c-1)] + x[idx(r,c+1)] - 4*x[idx(r,c)]; } for( int r = 1; r < rows-1; r++ ) { res[idx(r,0)] = x[idx(r-1,0)] + x[idx(r+1,0)] + x[idx(r,1)] - 3*x[idx(r,0)]; res[idx(r,cols-1)] = x[idx(r-1,cols-1)] + x[idx(r+1,cols-1)] + x[idx(r,cols-2)] - 3*x[idx(r,cols-1)]; } for( int c = 1; c < cols-1; c++ ) { res[idx(0,c)] = x[idx(1,c)] + x[idx(0,c-1)] + x[idx(0,c+1)] - 3*x[idx(0,c)]; res[idx(rows-1,c)] = x[idx(rows-2,c)] + x[idx(rows-1,c-1)] + x[idx(rows-1,c+1)] - 3*x[idx(rows-1,c)]; } res[idx(0,0)] = x[idx(1,0)] + x[idx(0,1)] - 2*x[idx(0,0)]; res[idx(rows-1,0)] = x[idx(rows-2,0)] + x[idx(rows-1,1)] - 2*x[idx(rows-1,0)]; res[idx(0,cols-1)] = x[idx(1,cols-1)] + x[idx(0,cols-2)] - 2*x[idx(0,cols-1)]; res[idx(rows-1,cols-1)] = x[idx(rows-2,cols-1)] + x[idx(rows-1,cols-2)] - 2*x[idx(rows-1,cols-1)]; } float snrm(unsigned long n, float sx[], int itol) { unsigned long i,isamax; float ans; if (itol <= 3) { ans = 0.0; for (i=1;i<=n;i++) ans += sx[i]*sx[i]; return sqrt(ans); } else { isamax=1; for (i=1;i<=n;i++) { if (fabs(sx[i]) > fabs(sx[isamax])) isamax=i; } return fabs(sx[isamax]); } } /** * Biconjugate Gradient Method * from Numerical Recipes in C */ void linbcg(unsigned long n, float b[], float x[], int itol, float tol, int itmax, int *iter, float *err) { unsigned long j; float ak,akden,bk,bkden,bknum,bnrm,dxnrm,xnrm,zm1nrm,znrm; float *p,*pp,*r,*rr,*z,*zz; p=new float[n+1]; pp=new float[n+1]; r=new float[n+1]; rr=new float[n+1]; z=new float[n+1]; zz=new float[n+1]; *iter=0; atimes(n,x,r,0); for (j=1;j<=n;j++) { r[j]=b[j]-r[j]; rr[j]=r[j]; } atimes(n,r,rr,0); // minimum residual znrm=1.0; if (itol == 1) bnrm=snrm(n,b,itol); else if (itol == 2) { asolve(n,b,z,0); bnrm=snrm(n,z,itol); } else if (itol == 3 || itol == 4) { asolve(n,b,z,0); bnrm=snrm(n,z,itol); asolve(n,r,z,0); znrm=snrm(n,z,itol); } else printf("illegal itol in linbcg"); asolve(n,r,z,0); while (*iter <= itmax) { ++(*iter); zm1nrm=znrm; asolve(n,rr,zz,1); for (bknum=0.0,j=1;j<=n;j++) bknum += z[j]*rr[j]; if (*iter == 1) { for (j=1;j<=n;j++) { p[j]=z[j]; pp[j]=zz[j]; } } else { bk=bknum/bkden; for (j=1;j<=n;j++) { p[j]=bk*p[j]+z[j]; pp[j]=bk*pp[j]+zz[j]; } } bkden=bknum; atimes(n,p,z,0); for (akden=0.0,j=1;j<=n;j++) akden += z[j]*pp[j]; ak=bknum/akden; atimes(n,pp,zz,1); for (j=1;j<=n;j++) { x[j] += ak*p[j]; r[j] -= ak*z[j]; rr[j] -= ak*zz[j]; } asolve(n,r,z,0); if (itol == 1 || itol == 2) { znrm=1.0; *err=snrm(n,r,itol)/bnrm; } else if (itol == 3 || itol == 4) { znrm=snrm(n,z,itol); if (fabs(zm1nrm-znrm) > EPS*znrm) { dxnrm=fabs(ak)*snrm(n,p,itol); *err=znrm/fabs(zm1nrm-znrm)*dxnrm; } else { *err=znrm/bnrm; continue; } xnrm=snrm(n,x,itol); if (*err <= 0.5*xnrm) *err /= xnrm; else { *err=znrm/bnrm; continue; } } // fprintf( stderr, "iter=%4d err=%12.6f\n",*iter,*err); if (*err <= tol) break; } delete [] p; delete [] pp; delete [] r; delete [] rr; delete [] z; delete [] zz; } //#undef EPS pfstools-2.2.0/src/tmo/fattal02/pde.h0000664000701400070140000000505414105165616016013 0ustar rkm38rkm38/** * @file pde.h * @brief Solving Partial Differential Equations * * Full Multigrid Algorithm and Successive Overrelaxation. * * @author Grzegorz Krawczyk * * * This file is a part of PFSTMO package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * $Id: pde.h,v 1.4 2012/03/31 16:49:49 tk255 Exp $ */ #ifndef _fmg_pde_h_ #define _fmg_pde_h_ #include "pfstmo.h" /// limit of iterations for successive overrelaxation #define SOR_MAXITS 5001 /** * @brief solve pde using full multrigrid algorithm * * @param F array with divergence * @param U [out] solution */ void solve_pde_multigrid(pfstmo::Array2D *F, pfstmo::Array2D *U); /** * @brief solve pde using successive overrelaxation * * @param F array with divergence * @param U [out] solution * @param maxits limit of iterations */ void solve_pde_sor(pfstmo::Array2D *F, pfstmo::Array2D *U, int maxits=SOR_MAXITS); /** * @brief solve poisson pde (Laplace U = F) using discrete cosine transform * * @param F array of the right hand side (contains div G in this example) * @param U [out] solution * @param adjust_bound, adjust boundary values of F to make pde solvable */ void solve_pde_fft(pfstmo::Array2D *F, pfstmo::Array2D *U, bool adjust_bound=false); /** * @brief returns the residual error of the solution U, ie norm(Laplace U - F) * * @param F [in] right hand side * @param U [in] solution */ float residual_pde(pfstmo::Array2D *U, pfstmo::Array2D *F); /** * @brief component wise error estimate of the fft solver given an image size * * @param width * @param height */ double error_estim_pde_fft(unsigned int width, unsigned int height); double error_estim_pde_fft_d(unsigned int width, unsigned int height); #endif pfstools-2.2.0/src/tmo/fattal02/pfstmo_fattal02.cpp0000664000701400070140000002024614105165616020603 0ustar rkm38rkm38/** * @file pfstmo_fattal02.cpp * @brief Tone map XYZ channels using Fattal02 model * * Gradient Domain High Dynamic Range Compression * R. Fattal, D. Lischinski, and M. Werman * In ACM Transactions on Graphics, 2002. * * * This file is a part of PFSTMO package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Grzegorz Krawczyk, * * $Id: pfstmo_fattal02.cpp,v 1.10 2012/06/22 12:01:32 rafm Exp $ */ #include #include #include #include #include #include #include #include #include "tmo_fattal02.h" using namespace std; #define PROG_NAME "pfstmo_fattal02" /// verbose mode bool verbose = false; class QuietException { }; void printHelp() { fprintf( stderr, PROG_NAME " (" PACKAGE_STRING ") : \n" "\t[--alpha ] [--beta ] \n" "\t[--gamma ] \n" "\t[--saturation ] \n" "\t[--noise ] \n" "\t[--detail-level ] \n" "\t[--black-point ] [--white-point ] \n" "\t[--multigrid] \n" "\t[--verbose] [--help] \n" "See man page for more information.\n" ); } void pfstmo_fattal02( int argc, char* argv[] ) { pfs::DOMIO pfsio; //--- default tone mapping parameters; float opt_alpha = 1.0f; float opt_beta = 0.9f; float opt_gamma = -1.0f; // not set (0.8 for fft solver, 1.0 otherwise) float opt_saturation=0.8f; float opt_noise = -1.0f; // not set int opt_detail_level=-1; // not set (2 for fft solver, 0 otherwise) float opt_black_point=0.1f; float opt_white_point=0.5f; // Use multigrid if FFTW lib not available #if !defined(HAVE_FFTW3) || !defined(HAVE_OpenMP) bool opt_fftsolver=false; #else bool opt_fftsolver=true; #endif static struct option cmdLineOptions[] = { { "help", no_argument, NULL, 'h' }, { "verbose", no_argument, NULL, 'v' }, { "multigrid", no_argument, NULL, 'm' }, { "alpha", required_argument, NULL, 'a' }, { "beta", required_argument, NULL, 'b' }, { "gamma", required_argument, NULL, 'g' }, { "saturation", required_argument, NULL, 's' }, { "noise", required_argument, NULL, 'n' }, { "detail-level", required_argument, NULL, 'd' }, { "white-point", required_argument, NULL, 'w' }, { "black-point", required_argument, NULL, 'l' }, { NULL, 0, NULL, 0 } }; int optionIndex = 0; while( 1 ) { int c = getopt_long (argc, argv, "hvma:b:g:s:n:d:w:k:", cmdLineOptions, &optionIndex); if( c == -1 ) break; switch( c ) { case 'h': printHelp(); throw QuietException(); case 'v': verbose = true; break; case 'm': opt_fftsolver = false; break; case 'a': opt_alpha = (float)strtod( optarg, NULL ); if( opt_alpha<=0.0f ) throw pfs::Exception("alpha value out of range, should be >0"); break; case 'b': opt_beta = (float)strtod( optarg, NULL ); if( opt_beta<=0.0f ) throw pfs::Exception("beta value out of range, should be >0"); break; case 'g': opt_gamma = (float)strtod( optarg, NULL ); if( opt_gamma<=0.0f || opt_gamma>=10.0 ) throw pfs::Exception("gamma value out of range, should be >0"); break; case 's': opt_saturation = (float)strtod( optarg, NULL ); if( opt_saturation<=0.0f || opt_saturation>1.0f ) throw pfs::Exception("saturation value out of range, should be 0..1"); break; case 'n': opt_noise = (float)strtod( optarg, NULL ); if( opt_noise<0.0f ) throw pfs::Exception("noise level value out of range, should be >=0"); break; case 'd': opt_detail_level = (int) strtod( optarg, NULL ); if( opt_detail_level<0 || opt_detail_level>9 ) throw pfs::Exception("detail-level value out of range, should be 0..9"); break; case 'w': opt_white_point = (float)strtod( optarg, NULL ); if( opt_white_point<0.0f || opt_white_point>=50.0f ) throw pfs::Exception("white-point value out of range, should be 0..50"); break; case 'k': opt_black_point = (float)strtod( optarg, NULL ); if( opt_black_point<0.0f || opt_black_point>=50.0f ) throw pfs::Exception("black-point value out of range, should be 0..50"); break; case '?': throw QuietException(); case ':': throw QuietException(); } } if( opt_fftsolver ) { // Default params for the fftsolver if(opt_detail_level==-1) opt_detail_level=3; // 2 for low res, 3 is better for high res if(opt_gamma==-1.0f) opt_gamma=0.8f; if(opt_noise==-1.0f) opt_noise=0.002f; } // adjust noise floor if not set by user if( opt_noise<0.0f ) opt_noise = opt_alpha*0.01; // set gamma and detail level to default values which produce the // same output as older programme versions using the multi-level pde solver if(opt_detail_level==-1) opt_detail_level=0; if(opt_gamma==-1.0f) opt_gamma=1.0f; VERBOSE_STR << "threshold gradient (alpha): " << opt_alpha << endl; VERBOSE_STR << "strengh of modification (beta): " << opt_beta << endl; VERBOSE_STR << "gamma: " << opt_gamma << endl; VERBOSE_STR << "noise floor: " << opt_noise << endl; VERBOSE_STR << "saturation: " << opt_saturation << endl; VERBOSE_STR << "detail level: " << opt_detail_level << endl; VERBOSE_STR << "white point: " << opt_white_point << "%" << endl; VERBOSE_STR << "black point: " << opt_black_point << "%" << endl; VERBOSE_STR << "use fft pde solver: " << opt_fftsolver << endl; while( true ) { pfs::Frame *frame = pfsio.readFrame( stdin ); if( frame == NULL ) break; // No more frames pfs::Channel *X, *Y, *Z; frame->getXYZChannels( X, Y, Z ); frame->getTags()->setString("LUMINANCE", "RELATIVE"); //--- if( Y==NULL || X==NULL || Z==NULL) throw pfs::Exception( "Missing X, Y, Z channels in the PFS stream" ); // tone mapping int w = Y->getCols(); int h = Y->getRows(); pfs::Array2DImpl* L = new pfs::Array2DImpl(w,h); tmo_fattal02(w, h, Y->getRawData(), L->getRawData(), opt_alpha, opt_beta, opt_gamma, opt_noise, opt_detail_level, opt_black_point, opt_white_point, opt_fftsolver); // in-place color space transform pfs::Array2DImpl *G = new pfs::Array2DImpl( w, h ); // copy for G to preserve Y pfs::Array2D *R = X, *B = Z; pfs::transformColorSpace( pfs::CS_XYZ, X, Y, Z, pfs::CS_RGB, R, G, B ); // Color correction for( int i=0; i < w*h; i++ ) { const float epsilon = 1e-4f; float y = std::max( (*Y)(i), epsilon ); float l = std::max( (*L)(i), epsilon ); (*R)(i) = powf( std::max((*R)(i)/y,0.f), opt_saturation ) * l; (*G)(i) = powf( std::max((*G)(i)/y,0.f), opt_saturation ) * l; (*B)(i) = powf( std::max((*B)(i)/y,0.f), opt_saturation ) * l; } pfs::transformColorSpace( pfs::CS_RGB, R, G, B, pfs::CS_XYZ, X, Y, Z ); delete G; delete L; //--- pfsio.writeFrame( frame, stdout ); pfsio.freeFrame( frame ); } } int main( int argc, char* argv[] ) { try { pfstmo_fattal02( argc, argv ); } catch( pfs::Exception ex ) { fprintf( stderr, PROG_NAME " error: %s\n", ex.getMessage() ); return EXIT_FAILURE; } catch( QuietException ex ) { return EXIT_FAILURE; } return EXIT_SUCCESS; } pfstools-2.2.0/src/tmo/mantiuk06/0002775000701400070140000000000014105165616015271 5ustar rkm38rkm38pfstools-2.2.0/src/tmo/mantiuk06/contrast_domain.h0000664000701400070140000000503514105165616020627 0ustar rkm38rkm38/** * @brief Contrast mapping TMO * * From: * * Rafal Mantiuk, Karol Myszkowski, Hans-Peter Seidel. * A Perceptual Framework for Contrast Processing of High Dynamic Range Images * In: ACM Transactions on Applied Perception 3 (3), pp. 286-308, 2006 * http://www.mpi-inf.mpg.de/~mantiuk/contrast_domain/ * * This file is a part of PFSTMO package. * ---------------------------------------------------------------------- * Copyright (C) 2007 Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Radoslaw Mantiuk, * @author Rafal Mantiuk, * Updated 2007/12/17 by Ed Brambley * * $Id: contrast_domain.h,v 1.7 2008/06/16 22:17:47 rafm Exp $ */ #ifndef CONTRAST_DOMAIN_H #define CONTRAST_DOMAIN_H #include "pfstmo.h" /** * @brief: Tone mapping algorithm [Mantiuk2006] * * @param R red channel * @param G green channel * @param B blue channel * @param Y luminance channel * @param contrastFactor contrast scaling factor (in 0-1 range) * @param saturationFactor color desaturation (in 0-1 range) * @param bcg true if to use BiConjugate Gradients, false if to use Conjugate Gradients * @param itmax maximum number of iterations for convergence (typically 50) * @param tol tolerence to get within for convergence (typically 1e-3) * @param progress_cb callback function that reports progress * @return PFSTMO_OK if tone-mapping was sucessful, PFSTMO_ABORTED if * it was stopped from a callback function and PFSTMO_ERROR if an * error was encountered. */ int tmo_mantiuk06_contmap( int cols, int rows, float* R, float* G, float* B, float* Y, float contrastFactor, float saturationFactor, bool bcg, int itmax = 200, float tol = 1e-3, pfstmo_progress_callback progress_cb = NULL); #endif pfstools-2.2.0/src/tmo/mantiuk06/CMakeLists.txt0000664000701400070140000000073714105165616020036 0ustar rkm38rkm38include_directories ("${PROJECT_BINARY_DIR}/" "${PROJECT_SOURCE_DIR}/src/pfs" "${PROJECT_SOURCE_DIR}/src/tmo/pfstmo") if( NOT HAS_GETOPT ) include_directories ("${GETOPT_INCLUDE}") endif( NOT HAS_GETOPT ) link_directories("${PROJECT_SOURCE_DIR}/src/pfs") set(TRG pfstmo_mantiuk06) add_executable(${TRG} ${TRG}.cpp contrast_domain.cpp "${GETOPT_OBJECT}") target_link_libraries(${TRG} pfs) install (TARGETS ${TRG} DESTINATION bin) install (FILES ${TRG}.1 DESTINATION ${MAN_DIR}) pfstools-2.2.0/src/tmo/mantiuk06/pfstmo_mantiuk06.10000664000701400070140000000621614105165616020564 0ustar rkm38rkm38.TH "pfstmo_mantiuk06" 1 .SH NAME pfstmo_mantiuk06 \- Tone mapping in the contrast domain .SH SYNOPSIS .B pfstmo_mantiuk06 [--\fBfactor\fR ] [--\fBequalize-contrast\fR ] [--\fBsaturation\fR ] [--\fBverbose\fR] [--\fBquiet\fR] [--\fBhelp\fR] .SH DESCRIPTION This command implements two tone mapping operators: \fIcontrast mapping\fR and \fIcontrast equalization\fR. Both operators can produce very sharp images (sometimes non-photorealistic) while minimizing contrast reversal (halo artifacts). More information on these operators can be found in: .IP .PD 0 Rafal Mantiuk, Karol Myszkowski, Hans-Peter Seidel. .IP A Perceptual Framework for Contrast Processing of High Dynamic Range Images .IP In: ACM Transactions on Applied Perception 3 (3), pp. 286-308, 2006. .IP .PD http://www.mpi-inf.mpg.de/~mantiuk/contrast_domain/ .PP This version contains several performance improvements made by Ed Brambley. .PP If you find this TMO useful in your research project, please cite the paper above. .PP The result of this TMO requires gamma correction. .SH OPTIONS .TP --\fBequalize-contrast\fR , -\fBe\fR Use the \fIcontrast equalization\fR algorithm. If this option is not specified, the \fIcontrast mapping\fR algorithm will be used. The \fIcontrast equalization\fR algorithm requires scaling parameter that controls the amount sharpenning, which can range from 0.1 (very sharp) to 1 (less sharp). .IP \fIcontrast equalization\fR results in very sharp, but also less natural images. \fIcontrast equalization\fR may also enhance noise in low-quality images. .TP --\fBfactor\fR , -\fBf\fR Contrast scaling factor (values 0-1) determines how much contrast magnitudes should be reduced. This option cannot be used with \fIequalize-contrast\fR. The lower value results in a sharper image. Default value: 0.3 .TP --\fBsaturation\fR , -\fBs\fR Saturation correction (values 0-2). The lower value results in stronger desaturation. Default value: 0.8 .TP --\fBverbose\fR, -\fBv\fR Print additional information during program execution. .TP --\fBquiet\fR, -\fBq\fR Do not display progress report. .TP --\fBhelp\fR, -\fBh\fR Print list of commandline options. .SH EXAMPLES .TP pfsin memorial.hdr | pfstmo_mantiuk06 -f 0.5 | pfsgamma -g 2.2 | pfsout memorial.png .IP Tone map an image using contrast mapping and save it in the PNG format. .TP pfsin memorial.hdr | pfstmo_mantiuk06 -e 0.5 | pfsgamma -g 2.2 | pfsout memorial.png .IP The same as above, but use the contrast equalization algorithm. .TP pfsin memorial.hdr | pfstmo_mantiuk06 -f 0.5 -s 1 | pfsview .IP For optimal results you can output the result to pfsview and manually adjust the dynamic range window. Then save the image in pfsview by selecting 'Save image...' from the 'Frame' menu or pressing 's'. .TP pfsin bridge.jpg --linear | pfsclamp --min 0.007 | pfstmo_mantiuk06 | pfsview .IP Enhance the low-dynamic range image 'bridge' and view the result. pfsclamp command reduces noise for low code values. .SH "SEE ALSO" .BR pfsgamma (1) .BR pfsclamp (1) .BR pfsin (1) .BR pfsout (1) .BR pfsview (1) .SH BUGS Please report bugs and comments to the pfstools discussion group (http://groups.google.com/group/pfstools). pfstools-2.2.0/src/tmo/mantiuk06/pfstmo_mantiuk06.cpp0000664000701400070140000001517714105165616021214 0ustar rkm38rkm38/** * @brief Contrast mapping TMO * * From: * * Rafal Mantiuk, Karol Myszkowski, Hans-Peter Seidel. * A Perceptual Framework for Contrast Processing of High Dynamic Range Images * In: ACM Transactions on Applied Perception 3 (3), pp. 286-308, 2006 * http://www.mpi-inf.mpg.de/~mantiuk/contrast_domain/ * * This file is a part of PFSTMO package. * ---------------------------------------------------------------------- * Copyright (C) 2007 Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Radoslaw Mantiuk, * @author Rafal Mantiuk, * Updated 2007/12/17 by Ed Brambley * * $Id: pfstmo_mantiuk06.cpp,v 1.10 2009/09/02 01:11:39 rafm Exp $ */ #include #include #include #include #include #include #include #include #include #include "contrast_domain.h" #define PROG_NAME "pfstmo_mantiuk06" class QuietException { }; using namespace std; void printHelp() { fprintf( stderr, PROG_NAME " (" PACKAGE_STRING ") : \n" "\t[--factor ] [--saturation ] [--equalize-contrast ]\n" "\t[--help] [--quiet] [--verbose]\n" "See man page for more information.\n" ); } bool verbose = false; bool quiet = false; int progress_report( int progress ) { if( !quiet ) { fprintf( stderr, "\rcompleted %d%%", progress ); if( progress == 100 ) fprintf( stderr, "\n" ); } return PFSTMO_CB_CONTINUE; } void tmo_mantiuk06(int argc, char * argv[]) { //--- default tone mapping parameters; float scaleFactor = 0.1f; float saturationFactor = 0.8f; bool cont_map = false, cont_eq = false, bcg = false; int itmax = 200; float tol = 1e-3; //--- process command line args static struct option cmdLineOptions[] = { { "help", no_argument, NULL, 'h' }, { "verbose", no_argument, NULL, 'v' }, { "bcg", no_argument, NULL, 'b' }, { "factor", required_argument, NULL, 'f' }, { "saturation", required_argument, NULL, 's' }, { "itmax", required_argument, NULL, 'm' }, { "tol", required_argument, NULL, 't' }, { "quiet", no_argument, NULL, 'q' }, { "equalize-contrast", required_argument, NULL, 'e' }, { NULL, 0, NULL, 0 } }; int optionIndex = 0; while( 1 ) { int c = getopt_long (argc, argv, "vhf:s:e:qbm:t:", cmdLineOptions, &optionIndex); if( c == -1 ) break; switch( c ) { case 'h': printHelp(); throw QuietException(); case 'q': quiet = true; break; case 'v': verbose = true; break; case 'b': bcg = true; break; case 'e': cont_eq = true; scaleFactor = 0.0f - (float)strtod( optarg, NULL ); if( scaleFactor > 0.0f ) throw pfs::Exception("incorrect contrast scale factor, accepted range is any positive number"); break; case 's': saturationFactor = (float)strtod( optarg, NULL ); if( saturationFactor < 0.0f || saturationFactor > 2.0f ) throw pfs::Exception("incorrect saturation factor, accepted range is (0..2)"); break; case 'f': cont_map = true; scaleFactor = (float)strtod( optarg, NULL ); if( scaleFactor < 0.0f || scaleFactor > 1.0f ) throw pfs::Exception("incorrect contrast scale factor, accepted range is (0..1)"); break; // case 'i': // interpolate_method = atoi( optarg ); // if (interpolate_method < 1 || interpolate_method > 3) // throw pfs::Exception("incorrect interpolation method, accepted values are 1, 2 or 3."); // break; case 'm': itmax = atoi( optarg ); if (itmax < 1) throw pfs::Exception("incorrect maximum number of iterations."); break; case 't': tol = (float)strtod( optarg, NULL ); if( tol < 0.0f || tol > 1.0f ) throw pfs::Exception("incorrect convergence tolerance, accepted range is (0..1)"); break; case '?': throw QuietException(); case ':': throw QuietException(); } } if( cont_eq && cont_map ) throw pfs::Exception( "the 'factor' parameter cannot be used in combination with contrast equalization" ); if( scaleFactor < 0 ) { VERBOSE_STR << "algorithm: contrast equalization" << endl; VERBOSE_STR << "contrast scale factor = " << -scaleFactor << endl; } else { VERBOSE_STR << "algorithm: contrast mapping" << endl; VERBOSE_STR << "contrast scale factor = " << scaleFactor << endl; } VERBOSE_STR << "saturation factor = " << saturationFactor << endl; if (bcg) { VERBOSE_STR << "using biconjugate gradients (itmax = " << itmax << ", tol = " << tol << ")." << endl; } else { VERBOSE_STR << "using conjugate gradients (itmax = " << itmax << ", tol = " << tol << ")." << endl; } pfs::DOMIO pfsio; while( true ) { pfs::Frame *frame = pfsio.readFrame( stdin ); if( frame == NULL ) break; pfs::Channel *inX, *inY, *inZ; frame->getXYZChannels(inX, inY, inZ); int cols = frame->getWidth(); int rows = frame->getHeight(); pfs::Array2DImpl R( cols, rows ); pfs::transformColorSpace( pfs::CS_XYZ, inX, inY, inZ, pfs::CS_RGB, inX, &R, inZ ); tmo_mantiuk06_contmap( cols, rows, inX->getRawData(), R.getRawData(), inZ->getRawData(), inY->getRawData(), scaleFactor, saturationFactor, bcg, itmax, tol, progress_report ); pfs::transformColorSpace( pfs::CS_RGB, inX, &R, inZ, pfs::CS_XYZ, inX, inY, inZ ); frame->getTags()->setString("LUMINANCE", "RELATIVE"); pfsio.writeFrame( frame, stdout ); pfsio.freeFrame(frame); } } int main( int argc, char* argv[] ) { try { tmo_mantiuk06( argc, argv ); } catch( pfs::Exception ex ) { fprintf( stderr, PROG_NAME " error: %s\n", ex.getMessage() ); return EXIT_FAILURE; } catch( QuietException ex ) { return EXIT_FAILURE; } return EXIT_SUCCESS; } pfstools-2.2.0/src/tmo/mantiuk06/contrast_domain.cpp0000664000701400070140000011312714105165616021164 0ustar rkm38rkm38/** * @Brief Contrast mapping TMO * * From: * * Rafal Mantiuk, Karol Myszkowski, Hans-Peter Seidel. * A Perceptual Framework for Contrast Processing of High Dynamic Range Images * In: ACM Transactions on Applied Perception 3 (3), pp. 286-308, 2006 * http://www.mpi-inf.mpg.de/~mantiuk/contrast_domain/ * * This file is a part of PFSTMO package. * ---------------------------------------------------------------------- * Copyright (C) 2007 Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Radoslaw Mantiuk, * @author Rafal Mantiuk, * Updated 2007/12/17 by Ed Brambley * (more information on the changes: * http://www.damtp.cam.ac.uk/user/ejb48/hdr/index.html) * Updated 2008/06/25 by Ed Brambley * bug fixes and openMP patches * more on this: * http://groups.google.com/group/pfstools/browse_thread/thread/de2378af98ec6185/0dee5304fc14e99d?hl=en#0dee5304fc14e99d * Optimization improvements by Lebed Dmytry * * $Id: contrast_domain.cpp,v 1.16 2012/04/22 13:14:14 rafm Exp $ */ #include #include #include #include #include "contrast_domain.h" #include "config.h" typedef struct pyramid_s { int rows; int cols; float* Gx; float* Gy; struct pyramid_s* next; struct pyramid_s* prev; } pyramid_t; extern float xyz2rgbD65Mat[3][3]; extern float rgb2xyzD65Mat[3][3]; #define PYRAMID_MIN_PIXELS 3 #define LOOKUP_W_TO_R 107 static void contrast_equalization( pyramid_t *pp, const float contrastFactor ); static void transform_to_luminance(pyramid_t* pyramid, float* const x, pfstmo_progress_callback progress_cb, const bool bcg); static void matrix_add(const int n, const float* const a, float* const b); static void matrix_subtract(const int n, const float* const a, float* const b); static void matrix_copy(const int n, const float* const a, float* const b); static void matrix_multiply_const(const int n, float* const a, const float val); static void matrix_divide(const int n, const float* const a, float* const b); static float* matrix_alloc(const int size); static void matrix_free(float* m); static float matrix_DotProduct(const int n, const float* const a, const float* const b); static void matrix_zero(const int n, float* const m); static void calculate_and_add_divergence(const int rows, const int cols, const float* const Gx, const float* const Gy, float* const divG); static void pyramid_calculate_divergence(pyramid_t* pyramid); static void pyramid_calculate_divergence_sum(pyramid_t* pyramid, float* divG_sum); static void calculate_scale_factor(const int n, const float* const G, float* const C); static void pyramid_calculate_scale_factor(pyramid_t* pyramid, pyramid_t* pC); static void scale_gradient(const int n, float* const G, const float* const C); static void pyramid_scale_gradient(pyramid_t* pyramid, pyramid_t* pC); static void pyramid_free(pyramid_t* pyramid); static pyramid_t* pyramid_allocate(const int cols, const int rows); static void calculate_gradient(const int cols, const int rows, const float* const lum, float* const Gx, float* const Gy); static void pyramid_calculate_gradient(pyramid_t* pyramid, float* lum); static void solveX(const int n, const float* const b, float* const x); static void multiplyA(pyramid_t* px, pyramid_t* pyramid, const float* const x, float* const divG_sum); static void linbcg(pyramid_t* pyramid, pyramid_t* pC, const float* const b, float* const x, const int itmax, const float tol, pfstmo_progress_callback progress_cb); static void lincg(pyramid_t* pyramid, pyramid_t* pC, const float* const b, float* const x, const int itmax, const float tol, pfstmo_progress_callback progress_cb); static float lookup_table(const int n, const float* const in_tab, const float* const out_tab, const float val); static void transform_to_R(const int n, float* const G); static void pyramid_transform_to_R(pyramid_t* pyramid); static void transform_to_G(const int n, float* const R); static void pyramid_transform_to_G(pyramid_t* pyramid); static void pyramid_gradient_multiply(pyramid_t* pyramid, const float val); static void dump_matrix_to_file(const int width, const int height, const float* const m, const char * const file_name); static void matrix_show(const char* const text, int rows, int cols, const float* const data); static void pyramid_show(pyramid_t* pyramid); static float W_table[] = {0.000000,0.010000,0.021180,0.031830,0.042628,0.053819,0.065556,0.077960,0.091140,0.105203,0.120255,0.136410,0.153788,0.172518,0.192739,0.214605,0.238282,0.263952,0.291817,0.322099,0.355040,0.390911,0.430009,0.472663,0.519238,0.570138,0.625811,0.686754,0.753519,0.826720,0.907041,0.995242,1.092169,1.198767,1.316090,1.445315,1.587756,1.744884,1.918345,2.109983,2.321863,2.556306,2.815914,3.103613,3.422694,3.776862,4.170291,4.607686,5.094361,5.636316,6.240338,6.914106,7.666321,8.506849,9.446889,10.499164,11.678143,13.000302,14.484414,16.151900,18.027221,20.138345,22.517282,25.200713,28.230715,31.655611,35.530967,39.920749,44.898685,50.549857,56.972578,64.280589,72.605654,82.100619,92.943020,105.339358,119.530154,135.795960,154.464484,175.919088,200.608905,229.060934,261.894494,299.838552,343.752526,394.651294,453.735325,522.427053,602.414859,695.706358,804.693100,932.229271,1081.727632,1257.276717,1463.784297,1707.153398,1994.498731,2334.413424,2737.298517,3215.770944,3785.169959,4464.187290,5275.653272,6247.520102,7414.094945,8817.590551,10510.080619}; static float R_table[] = {0.000000,0.009434,0.018868,0.028302,0.037736,0.047170,0.056604,0.066038,0.075472,0.084906,0.094340,0.103774,0.113208,0.122642,0.132075,0.141509,0.150943,0.160377,0.169811,0.179245,0.188679,0.198113,0.207547,0.216981,0.226415,0.235849,0.245283,0.254717,0.264151,0.273585,0.283019,0.292453,0.301887,0.311321,0.320755,0.330189,0.339623,0.349057,0.358491,0.367925,0.377358,0.386792,0.396226,0.405660,0.415094,0.424528,0.433962,0.443396,0.452830,0.462264,0.471698,0.481132,0.490566,0.500000,0.509434,0.518868,0.528302,0.537736,0.547170,0.556604,0.566038,0.575472,0.584906,0.594340,0.603774,0.613208,0.622642,0.632075,0.641509,0.650943,0.660377,0.669811,0.679245,0.688679,0.698113,0.707547,0.716981,0.726415,0.735849,0.745283,0.754717,0.764151,0.773585,0.783019,0.792453,0.801887,0.811321,0.820755,0.830189,0.839623,0.849057,0.858491,0.867925,0.877358,0.886792,0.896226,0.905660,0.915094,0.924528,0.933962,0.943396,0.952830,0.962264,0.971698,0.981132,0.990566,1.000000}; // display matrix in the console (debugging) static void matrix_show(const char* const text, int cols, int rows, const float* const data) { const int _cols = cols; if(rows > 8) rows = 8; if(cols > 8) cols = 8; printf("\n%s\n", text); for(int ky=0; kynext != NULL) pyramid = pyramid->next; while (pyramid != NULL) { printf("\n----- pyramid_t level %d,%d\n", pyramid->cols, pyramid->rows); sprintf(ss, "Gx %p ", pyramid->Gx); if(pyramid->Gx != NULL) matrix_show(ss,pyramid->cols, pyramid->rows, pyramid->Gx); sprintf(ss, "Gy %p ", pyramid->Gy); if(pyramid->Gy != NULL) matrix_show(ss,pyramid->cols, pyramid->rows, pyramid->Gy); pyramid = pyramid->prev; } } static inline float max( float a, float b ) { return a > b ? a : b; } static inline float min( float a, float b ) { return a < b ? a : b; } static inline int imin(int a, int b) { return a < b ? a : b; } // upsample the matrix // upsampled matrix is twice bigger in each direction than data[] // res should be a pointer to allocated memory for bigger matrix // cols and rows are the dimmensions of the output matrix static void matrix_upsample(const int outCols, const int outRows, const float* const in, float* const out) { const int inRows = outRows/2; const int inCols = outCols/2; // Transpose of experimental downsampling matrix (theoretically the correct thing to do) const float dx = (float)inCols / ((float)outCols); const float dy = (float)inRows / ((float)outRows); const float factor = 1.0f / (dx*dy); // This gives a genuine upsampling matrix, not the transpose of the downsampling matrix // const float factor = 1.0f; // Theoretically, this should be the best. #pragma omp parallel for schedule(static) for (int y = 0; y < outRows; y++) { const float sy = y * dy; const int iy1 = ( y * inRows) / outRows; const int iy2 = imin(((y+1) * inRows) / outRows, inRows-1); for (int x = 0; x < outCols; x++) { const float sx = x * dx; const int ix1 = ( x * inCols) / outCols; const int ix2 = imin(((x+1) * inCols) / outCols, inCols-1); out[x + y*outCols] = ( ((ix1+1) - sx)*((iy1+1 - sy)) * in[ix1 + iy1*inCols] + ((ix1+1) - sx)*(sy+dy - (iy1+1)) * in[ix1 + iy2*inCols] + (sx+dx - (ix1+1))*((iy1+1 - sy)) * in[ix2 + iy1*inCols] + (sx+dx - (ix1+1))*(sy+dx - (iy1+1)) * in[ix2 + iy2*inCols])*factor; } } } // downsample the matrix static void matrix_downsample(const int inCols, const int inRows, const float* const data, float* const res) { const int outRows = inRows / 2; const int outCols = inCols / 2; const float dx = (float)inCols / ((float)outCols); const float dy = (float)inRows / ((float)outRows); // New downsampling by Ed Brambley: // Experimental downsampling that assumes pixels are square and // integrates over each new pixel to find the average value of the // underlying pixels. // // Consider the original pixels laid out, and the new (larger) // pixels layed out over the top of them. Then the new value for // the larger pixels is just the integral over that pixel of what // shows through; i.e., the values of the pixels underneath // multiplied by how much of that pixel is showing. // // (ix1, iy1) is the coordinate of the top left visible pixel. // (ix2, iy2) is the coordinate of the bottom right visible pixel. // (fx1, fy1) is the fraction of the top left pixel showing. // (fx2, fy2) is the fraction of the bottom right pixel showing. const float normalize = 1.0f/(dx*dy); #pragma omp parallel for schedule(static) for (int y = 0; y < outRows; y++) { const int iy1 = ( y * inRows) / outRows; const int iy2 = ((y+1) * inRows) / outRows; const float fy1 = (iy1+1) - y * dy; const float fy2 = (y+1) * dy - iy2; for (int x = 0; x < outCols; x++) { const int ix1 = ( x * inCols) / outCols; const int ix2 = ((x+1) * inCols) / outCols; const float fx1 = (ix1+1) - x * dx; const float fx2 = (x+1) * dx - ix2; float pixVal = 0.0f; float factorx, factory; for (int i = iy1; i <= iy2 && i < inRows; i++) { if (i == iy1) factory = fy1; // We're just getting the bottom edge of this pixel else if (i == iy2) factory = fy2; // We're just gettting the top edge of this pixel else factory = 1.0f; // We've got the full height of this pixel for (int j = ix1; j <= ix2 && j < inCols; j++) { if (j == ix1) factorx = fx1; // We've just got the right edge of this pixel else if (j == ix2) factorx = fx2; // We've just got the left edge of this pixel else factorx = 1.0f; // We've got the full width of this pixel pixVal += data[j + i*inCols] * factorx * factory; } } res[x + y * outCols] = pixVal * normalize; // Normalize by the area of the new pixel } } } // return = a + b static inline void matrix_add(const int n, const float* const a, float* const b) { #pragma omp parallel for schedule(static) for(int i=0; irows*pyramid->cols); // Find the coarsest pyramid, and the number of pyramid levels int levels = 1; while (pyramid->next != NULL) { levels++; pyramid = pyramid->next; } // For every level, we swap temp and divG_sum. So, if there are an odd number of levels... if (levels % 2) { float* const dummy = divG_sum; divG_sum = temp; temp = dummy; } // Add them all together while (pyramid != NULL) { // Upsample or zero as needed if (pyramid->next != NULL) matrix_upsample(pyramid->cols, pyramid->rows, divG_sum, temp); else matrix_zero(pyramid->rows * pyramid->cols, temp); // Add in the (freshly calculated) divergences calculate_and_add_divergence(pyramid->cols, pyramid->rows, pyramid->Gx, pyramid->Gy, temp); // char name[256]; // sprintf( name, "Up_%d.pfs", pyramid->cols ); // dump_matrix_to_file( pyramid->cols, pyramid->rows, temp, name ); // matrix_copy(pyramid->rows*pyramid->cols, temp, divG_sum); // Rather than copying, just switch round the pointers: we know we get them the right way round at the end. float* const dummy = divG_sum; divG_sum = temp; temp = dummy; pyramid = pyramid->prev; } matrix_free(temp); } // calculate scale factors (Cx,Cy) for gradients (Gx,Gy) // C is equal to EDGE_WEIGHT for gradients smaller than GFIXATE or 1.0 otherwise static inline void calculate_scale_factor(const int n, const float* const G, float* const C) { float GFIXATE = 0.1f; float EDGE_WEIGHT = 0.01f; const float detectT = 0.001f; const float a = 0.038737; const float b = 0.537756; #pragma omp parallel for schedule(static) for(int i=0; irows * pyramid->cols; calculate_scale_factor(size, pyramid->Gx, pC->Gx); calculate_scale_factor(size, pyramid->Gy, pC->Gy); pyramid = pyramid->next; pC = pC->next; } } // Scale gradient (Gx and Gy) by C (Cx and Cy) // G = G / C static inline void scale_gradient(const int n, float* const G, const float* const C) { #pragma omp parallel for schedule(static) for(int i=0; irows * pyramid->cols; scale_gradient(size, pyramid->Gx, pC->Gx); scale_gradient(size, pyramid->Gy, pC->Gy); pyramid = pyramid->next; pC = pC->next; } } // free memory allocated for the pyramid static void pyramid_free(pyramid_t* pyramid) { while (pyramid) { if(pyramid->Gx != NULL) { free(pyramid->Gx); pyramid->Gx = NULL; } if(pyramid->Gy != NULL) { free(pyramid->Gy); pyramid->Gy = NULL; } pyramid_t* const next = pyramid->next; pyramid->prev = NULL; pyramid->next = NULL; free(pyramid); pyramid = next; } } // allocate memory for the pyramid static pyramid_t * pyramid_allocate(int cols, int rows) { pyramid_t* level = NULL; pyramid_t* pyramid = NULL; pyramid_t* prev = NULL; while(rows >= PYRAMID_MIN_PIXELS && cols >= PYRAMID_MIN_PIXELS) { level = (pyramid_t *) malloc(sizeof(pyramid_t)); if(level == NULL) { fprintf(stderr, "ERROR: malloc in pyramid_alloc() (size:%d)", (int)sizeof(pyramid_t)); exit(155); } memset( level, 0, sizeof(pyramid_t) ); level->rows = rows; level->cols = cols; const int size = level->rows * level->cols; level->Gx = matrix_alloc(size); level->Gy = matrix_alloc(size); level->prev = prev; if(prev != NULL) prev->next = level; prev = level; if(pyramid == NULL) pyramid = level; rows /= 2; cols /= 2; } return pyramid; } // calculate gradients static inline void calculate_gradient(const int cols, const int rows, const float* const lum, float* const Gx, float* const Gy) { #pragma omp parallel for schedule(static) for(int ky=0; kyrows/2)*(pyramid->cols/2)); float* const temp_saved = temp; calculate_gradient(pyramid->cols, pyramid->rows, lum_temp, pyramid->Gx, pyramid->Gy); pyramid = pyramid->next; // int l = 1; while(pyramid) { matrix_downsample(pyramid->prev->cols, pyramid->prev->rows, lum_temp, temp); // fprintf( stderr, "%d x %d -> %d x %d\n", pyramid->cols, pyramid->rows, // prev_pp->cols, prev_pp->rows ); // char name[40]; // sprintf( name, "ds_%d.pfs", l++ ); // dump_matrix_to_file( pyramid->cols, pyramid->rows, temp, name ); calculate_gradient(pyramid->cols, pyramid->rows, temp, pyramid->Gx, pyramid->Gy); float* const dummy = lum_temp; lum_temp = temp; temp = dummy; pyramid = pyramid->next; } matrix_free(temp_saved); } // x = -0.25 * b static inline void solveX(const int n, const float* const b, float* const x) { #pragma omp parallel for schedule(static) for (int i=0; irows*pC->cols, x, divG_sum); // use divG_sum as a temp variable pyramid_calculate_gradient(px, divG_sum); pyramid_scale_gradient(px, pC); // scale gradients by Cx,Cy from main pyramid pyramid_calculate_divergence_sum(px, divG_sum); // calculate the sum of divergences } // bi-conjugate linear equation solver // overwrites pyramid! static void linbcg(pyramid_t* pyramid, pyramid_t* pC, float* const b, float* const x, const int itmax, const float tol, pfstmo_progress_callback progress_cb) { const int rows = pyramid->rows; const int cols = pyramid->cols; const int n = rows*cols; const float tol2 = tol*tol; float* const z = matrix_alloc(n); float* const zz = matrix_alloc(n); float* const p = matrix_alloc(n); float* const pp = matrix_alloc(n); float* const r = matrix_alloc(n); float* const rr = matrix_alloc(n); float* const x_save = matrix_alloc(n); const float bnrm2 = matrix_DotProduct(n, b, b); multiplyA(pyramid, pC, x, r); // r = A*x = divergence(x) matrix_subtract(n, b, r); // r = b - r float err2 = matrix_DotProduct(n, r, r); // err2 = r.r // matrix_copy(n, r, rr); // rr = r multiplyA(pyramid, pC, r, rr); // rr = A*r float bkden = 0; float saved_err2 = err2; matrix_copy(n, x, x_save); const float ierr2 = err2; const float percent_sf = 100.0f/logf(tol2*bnrm2/ierr2); int iter = 0; bool reset = true; int num_backwards = 0; const int num_backwards_ceiling = 3; for (; iter < itmax; iter++) { fprintf( stderr, "Iter %d\n", iter ); if( progress_cb != NULL ) progress_cb( (int) (logf(err2/ierr2)*percent_sf)); solveX(n, r, z); // z = ~A(-1) * r = -0.25 * r solveX(n, rr, zz); // zz = ~A(-1) * rr = -0.25 * rr const float bknum = matrix_DotProduct(n, z, rr); if(reset) { reset = false; matrix_copy(n, z, p); matrix_copy(n, zz, pp); } else { const float bk = bknum / bkden; // beta = ... #pragma omp parallel for schedule(static) for (int i = 0; i < n; i++) { p[i] = z[i] + bk * p[i]; pp[i] = zz[i] + bk * pp[i]; } } bkden = bknum; // numerato becomes the dominator for the next iteration multiplyA(pyramid, pC, p, z); // z = A* p = divergence( p) multiplyA(pyramid, pC, pp, zz); // zz = A*pp = divergence(pp) const float ak = bknum / matrix_DotProduct(n, z, pp); // alfa = ... #pragma omp parallel for schedule(static) for(int i = 0 ; i < n ; i++ ) { r[i] -= ak * z[i]; // r = r - alfa * z rr[i] -= ak * zz[i]; //rr = rr - alfa * zz } const float old_err2 = err2; err2 = matrix_DotProduct(n, r, r); // Have we gone unstable? if (err2 > old_err2) { // Save where we've got to if it's the best yet if (num_backwards == 0 && old_err2 < saved_err2) { saved_err2 = old_err2; matrix_copy(n, x, x_save); } num_backwards++; } else { num_backwards = 0; } #pragma omp parallel for schedule(static) for(int i = 0 ; i < n ; i++ ) x[i] += ak * p[i]; // x = x + alfa * p if (num_backwards > num_backwards_ceiling) { // Reset reset = true; num_backwards = 0; // Recover saved value matrix_copy(n, x_save, x); // r = Ax multiplyA(pyramid, pC, x, r); // r = b - r matrix_subtract(n, b, r); // err2 = r.r err2 = matrix_DotProduct(n, r, r); saved_err2 = err2; // rr = A*r multiplyA(pyramid, pC, r, rr); } // fprintf(stderr, "iter:%d err:%f\n", iter+1, sqrtf(err2/bnrm2)); if(err2/bnrm2 < tol2) break; } // Use the best version we found if (err2 > saved_err2) { err2 = saved_err2; matrix_copy(n, x_save, x); } if (err2/bnrm2 > tol2) { // Not converged if( progress_cb != NULL ) progress_cb( (int) (logf(err2/ierr2)*percent_sf)); if (iter == itmax) fprintf(stderr, "\npfstmo_mantiuk06: Warning: Not converged (hit maximum iterations), error = %g (should be below %g).\n", sqrtf(err2/bnrm2), tol); else fprintf(stderr, "\npfstmo_mantiuk06: Warning: Not converged (going unstable), error = %g (should be below %g).\n", sqrtf(err2/bnrm2), tol); } else if (progress_cb != NULL) progress_cb(100); matrix_free(x_save); matrix_free(p); matrix_free(pp); matrix_free(z); matrix_free(zz); matrix_free(r); matrix_free(rr); } // conjugate linear equation solver // overwrites pyramid! static void lincg(pyramid_t* pyramid, pyramid_t* pC, const float* const b, float* const x, const int itmax, const float tol, pfstmo_progress_callback progress_cb) { const int rows = pyramid->rows; const int cols = pyramid->cols; const int n = rows*cols; const float tol2 = tol*tol; float* const x_save = matrix_alloc(n); float* const r = matrix_alloc(n); float* const p = matrix_alloc(n); float* const Ap = matrix_alloc(n); // bnrm2 = ||b|| const float bnrm2 = matrix_DotProduct(n, b, b); // r = b - Ax multiplyA(pyramid, pC, x, r); matrix_subtract(n, b, r); float rdotr = matrix_DotProduct(n, r, r); // rdotr = r.r // p = r matrix_copy(n, r, p); // Setup initial vector float saved_rdotr = rdotr; matrix_copy(n, x, x_save); const float irdotr = rdotr; const float percent_sf = 100.0f/logf(tol2*bnrm2/irdotr); int iter = 0; int num_backwards = 0; const int num_backwards_ceiling = 3; for (; iter < itmax; iter++) { if( progress_cb != NULL ) { int ret = progress_cb( (int) (logf(rdotr/irdotr)*percent_sf)); if( ret == PFSTMO_CB_ABORT && iter > 0 ) // User requested abort break; } // Ap = A p multiplyA(pyramid, pC, p, Ap); // alpha = r.r / (p . Ap) const float alpha = rdotr / matrix_DotProduct(n, p, Ap); // r = r - alpha Ap #pragma omp parallel for schedule(static) for (int i = 0; i < n; i++) r[i] -= alpha * Ap[i]; // rdotr = r.r const float old_rdotr = rdotr; rdotr = matrix_DotProduct(n, r, r); // Have we gone unstable? if (rdotr > old_rdotr) { // Save where we've got to if (num_backwards == 0 && old_rdotr < saved_rdotr) { saved_rdotr = old_rdotr; matrix_copy(n, x, x_save); } num_backwards++; } else { num_backwards = 0; } // x = x + alpha p #pragma omp parallel for schedule(static) for (int i = 0; i < n; i++) x[i] += alpha * p[i]; // Exit if we're done // fprintf(stderr, "iter:%d err:%f\n", iter+1, sqrtf(rdotr/bnrm2)); if(rdotr/bnrm2 < tol2) break; if (num_backwards > num_backwards_ceiling) { // Reset num_backwards = 0; matrix_copy(n, x_save, x); // r = Ax multiplyA(pyramid, pC, x, r); // r = b - r matrix_subtract(n, b, r); // rdotr = r.r rdotr = matrix_DotProduct(n, r, r); saved_rdotr = rdotr; // p = r matrix_copy(n, r, p); } else { // p = r + beta p const float beta = rdotr/old_rdotr; #pragma omp parallel for schedule(static) for (int i = 0; i < n; i++) p[i] = r[i] + beta*p[i]; } } // Use the best version we found if (rdotr > saved_rdotr) { rdotr = saved_rdotr; matrix_copy(n, x_save, x); } if (rdotr/bnrm2 > tol2) { // Not converged if( progress_cb != NULL ) progress_cb( (int) (logf(rdotr/irdotr)*percent_sf)); if (iter == itmax) fprintf(stderr, "\npfstmo_mantiuk06: Warning: Not converged (hit maximum iterations), error = %g (should be below %g).\n", sqrtf(rdotr/bnrm2), tol); else fprintf(stderr, "\npfstmo_mantiuk06: Warning: Not converged (going unstable), error = %g (should be below %g).\n", sqrtf(rdotr/bnrm2), tol); } else if (progress_cb != NULL) progress_cb(100); matrix_free(x_save); matrix_free(p); matrix_free(Ap); matrix_free(r); } // in_tab and out_tab should contain inccreasing float values static inline float lookup_table(const int n, const float* const in_tab, const float* const out_tab, const float val) { if(unlikely(val < in_tab[0])) return out_tab[0]; for (int j = 1; j < n; j++) if(val < in_tab[j]) { const float dd = (val - in_tab[j-1]) / (in_tab[j] - in_tab[j-1]); return out_tab[j-1] + (out_tab[j] - out_tab[j-1]) * dd; } return out_tab[n-1]; } // transform gradient (Gx,Gy) to R static inline void transform_to_R(const int n, float* const G) { #pragma omp parallel for schedule(static) for(int j=0;jrows * pyramid->cols; transform_to_R(size, pyramid->Gx); transform_to_R(size, pyramid->Gy); pyramid = pyramid->next; } } // transform from R to G static inline void transform_to_G(const int n, float* const R){ #pragma omp parallel for schedule(static) for(int j=0;jrows*pyramid->cols, pyramid->Gx); transform_to_G(pyramid->rows*pyramid->cols, pyramid->Gy); pyramid = pyramid->next; } } // multiply gradient (Gx,Gy) values by float scalar value for the whole pyramid static inline void pyramid_gradient_multiply(pyramid_t* pyramid, const float val) { while (pyramid != NULL) { matrix_multiply_const(pyramid->rows*pyramid->cols, pyramid->Gx, val); matrix_multiply_const(pyramid->rows*pyramid->cols, pyramid->Gy, val); pyramid = pyramid->next; } } static int sort_float(const void* const v1, const void* const v2) { if (*((float*)v1) < *((float*)v2)) return -1; if(likely(*((float*)v1) > *((float*)v2))) return 1; return 0; } // transform gradients to luminance static void transform_to_luminance(pyramid_t* pp, float* const x, pfstmo_progress_callback progress_cb, const bool bcg, const int itmax, const float tol) { pyramid_t* pC = pyramid_allocate(pp->cols, pp->rows); pyramid_calculate_scale_factor(pp, pC); // calculate (Cx,Cy) pyramid_scale_gradient(pp, pC); // scale small gradients by (Cx,Cy); float* b = matrix_alloc(pp->cols * pp->rows); pyramid_calculate_divergence_sum(pp, b); // calculate the sum of divergences (equal to b) // calculate luminances from gradients if (bcg) linbcg(pp, pC, b, x, itmax, tol, progress_cb); else lincg(pp, pC, b, x, itmax, tol, progress_cb); matrix_free(b); pyramid_free(pC); } struct hist_data { float size; float cdf; int index; }; static int hist_data_order(const void* const v1, const void* const v2) { if (((struct hist_data*) v1)->size < ((struct hist_data*) v2)->size) return -1; if (((struct hist_data*) v1)->size > ((struct hist_data*) v2)->size) return 1; return 0; } static int hist_data_index(const void* const v1, const void* const v2) { return ((struct hist_data*) v1)->index - ((struct hist_data*) v2)->index; } static void contrast_equalization( pyramid_t *pp, const float contrastFactor ) { // Count sizes int total_pixels = 0; pyramid_t* l = pp; while (l != NULL) { total_pixels += l->rows * l->cols; l = l->next; } // Allocate memory struct hist_data* hist = (struct hist_data*) malloc(sizeof(struct hist_data) * total_pixels); if (hist == NULL) { fprintf(stderr, "ERROR: malloc in contrast_equalization() (size:%d)", (int)sizeof(struct hist_data) * total_pixels); exit(155); } // Build histogram info l = pp; int index = 0; while( l != NULL ) { const int pixels = l->rows*l->cols; const int offset = index; #pragma omp parallel for schedule(static) for(int c = 0; c < pixels; c++) { hist[c+offset].size = sqrtf( l->Gx[c]*l->Gx[c] + l->Gy[c]*l->Gy[c] ); hist[c+offset].index = c + offset; } index += pixels; l = l->next; } // Generate histogram qsort(hist, total_pixels, sizeof(struct hist_data), hist_data_order); // Calculate cdf const float norm = 1.0f / (float) total_pixels; #pragma omp parallel for schedule(static) for (int i = 0; i < total_pixels; i++) hist[i].cdf = ((float) i) * norm; // Recalculate in terms of indexes qsort(hist, total_pixels, sizeof(struct hist_data), hist_data_index); //Remap gradient magnitudes l = pp; index = 0; while( l != NULL ) { const int pixels = l->rows*l->cols; const int offset = index; #pragma omp parallel for schedule(static) for( int c = 0; c < pixels; c++) { const float scale = contrastFactor * hist[c+offset].cdf/hist[c+offset].size; l->Gx[c] *= scale; l->Gy[c] *= scale; } index += pixels; l = l->next; } free(hist); } // tone mapping int tmo_mantiuk06_contmap(const int c, const int r, float* const R, float* const G, float* const B, float* const Y, const float contrastFactor, const float saturationFactor, const bool bcg, const int itmax, const float tol, pfstmo_progress_callback progress_cb) { const int n = c*r; /* Normalize */ float Ymax = Y[0]; for (int j = 1; j < n; j++) if (Y[j] > Ymax) Ymax = Y[j]; const float clip_min = 1e-7f*Ymax; #pragma omp parallel for schedule(static) for (int j = 0; j < n; j++) { if( unlikely(R[j] < clip_min) ) R[j] = clip_min; if( unlikely(G[j] < clip_min) ) G[j] = clip_min; if( unlikely(B[j] < clip_min) ) B[j] = clip_min; if( unlikely(Y[j] < clip_min) ) Y[j] = clip_min; } #pragma omp parallel for schedule(static) for(int j=0;j 0.0f ) pyramid_gradient_multiply(pp, contrastFactor); // Contrast mapping else contrast_equalization(pp, -contrastFactor); // Contrast equalization pyramid_transform_to_G(pp); // transform R to gradients transform_to_luminance(pp, Y, progress_cb, bcg, itmax, tol); // transform gradients to luminance Y pyramid_free(pp); /* Renormalize luminance */ float* temp = matrix_alloc(n); matrix_copy(n, Y, temp); // copy Y to temp qsort(temp, n, sizeof(float), sort_float); // sort temp in ascending order // const float median = (temp[(int)((n-1)/2)] + temp[(int)((n-1)/2+1)]) * 0.5f; // calculate median const float CUT_MARGIN = 0.1f; float trim = (n-1) * CUT_MARGIN * 0.01f; float delta = trim - floorf(trim); const float l_min = temp[(int)floorf(trim)] * delta + temp[(int)ceilf(trim)] * (1.0f-delta); trim = (n-1) * (100.0f - CUT_MARGIN) * 0.01f; delta = trim - floorf(trim); const float l_max = temp[(int)floorf(trim)] * delta + temp[(int)ceilf(trim)] * (1.0f-delta); matrix_free(temp); const float disp_dyn_range = 2.3f; #pragma omp parallel for schedule(static) for(int j=0;j * * $Id: tmo_reinhard02.h,v 1.2 2008/09/04 12:46:49 julians37 Exp $ */ #ifndef _tmo_reinhard02_h_ #define _tmo_reinhard02_h_ /* * @brief Photographic tone-reproduction * * @param width image width * @param height image height * @param Y input luminance * @param L output tonemapped intensities * @param use_scales true: local version, false: global version of TMO * @param key maps log average luminance to this value (default: 0.18) * @param phi sharpening parameter (defaults to 1 - no sharpening) * @param num number of scales to use in computation (default: 8) * @param low size in pixels of smallest scale (should be kept at 1) * @param high size in pixels of largest scale (default 1.6^8 = 43) */ void tmo_reinhard02(unsigned int width, unsigned int height, const float *Y, float *L, bool use_scales, float key, float phi, int num, int low, int high, bool temporal_coherent ); #endif /* _tmo_reinhard02_h_ */ pfstools-2.2.0/src/tmo/reinhard02/pfstmo_reinhard02.10000664000701400070140000000365214105165616021025 0ustar rkm38rkm38.TH "pfstmo_reinhard02" 1 .SH NAME pfstmo_reinhard02 \- Photographic Tone Reproduction for Digital Images .SH SYNOPSIS .B pfstmo_reinhard02 [--scales] [--key ] [--phi ] [--range ] [--lower ] [--upper ] [--temporal-coherent] [--verbose] [--help] .SH DESCRIPTION This command implements a tone mapping operator as described in: Photographic Tone Reproduction for Digital Images. E. Reinhard, M. Stark, P. Shirley, and J. Ferwerda. In ACM Transactions on Graphics, 2002. According to the paper, results of this TMO require gamma correction. .SH OPTIONS .TP [--scales] Use scales to calculate local adaptation. That means: use local version of this operator. By default, global version is used. .TP [--key ] Set key value for the image (refer to paper for details). Default value: 0.18, accepted range <0..1>. .TP [--phi ] Set phi value (refer to paper for details). Default value: 1.0, accepted range >=0.0. .TP [--range ] Set range size (refer to paper for details). Default value: 8, accepted range >1. .TP [--lower ] Set lower scale size (refer to paper for details). Default value: 1, accepted range >=1. .TP [--upper ] Set upper scale size (refer to paper for details). Default value: 43, accepted range >=1. .TP [--temporal-coherent], [-t] When this option is specified, tone mapping is time coherent for video sequences. Currently the option imposes that the average and maximum luminance value can change at most by 1% between two consecutive frames. .TP --verbose Print additional information during program execution. .TP --help Print list of commandline options. .SH EXAMPLES .TP pfsin memorial.hdr | pfstmo_reinhard02 -s | pfsgamma -g 2.2 | pfsout memorial.png Tone map image and save it in png format. .SH "SEE ALSO" .BR pfsgamma (1) .BR pfsin (1) .BR pfsout (1) .BR pfsview (1) .SH BUGS Please report bugs and comments on implementation to Grzegorz Krawczyk . pfstools-2.2.0/src/tmo/reinhard02/CMakeLists.txt0000664000701400070140000000075214105165616020153 0ustar rkm38rkm38include_directories ("${PROJECT_BINARY_DIR}/" "${PROJECT_SOURCE_DIR}/src/pfs" "${PROJECT_SOURCE_DIR}/src/tmo/pfstmo") if( NOT HAS_GETOPT ) include_directories ("${GETOPT_INCLUDE}") endif( NOT HAS_GETOPT ) link_directories("${PROJECT_SOURCE_DIR}/src/pfs") set(TRG pfstmo_reinhard02) add_executable(${TRG} ${TRG}.cpp tmo_reinhard02.cpp approx.cpp "${GETOPT_OBJECT}") target_link_libraries(${TRG} pfs) install (TARGETS ${TRG} DESTINATION bin) install (FILES ${TRG}.1 DESTINATION ${MAN_DIR}) pfstools-2.2.0/src/tmo/reinhard02/pfstmo_reinhard02.cpp0000664000701400070140000001316114105165616021443 0ustar rkm38rkm38/** * @file pfstmo_reinhard02.cpp * @brief Tone map XYZ channels using Reinhard02 model * * Photographic Tone Reproduction for Digital Images. * E. Reinhard, M. Stark, P. Shirley, and J. Ferwerda. * In ACM Transactions on Graphics, 2002. * * * This file is a part of PFSTMO package. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Grzegorz Krawczyk, * * $Id: pfstmo_reinhard02.cpp,v 1.3 2008/09/04 12:46:49 julians37 Exp $ */ #include #include #include #include #include #include #include #include "tmo_reinhard02.h" using namespace std; #define PROG_NAME "pfstmo_reinhard02" /// verbose mode bool verbose = false; class QuietException { }; void printHelp() { fprintf( stderr, PROG_NAME " (" PACKAGE_STRING ") : \n" "\t[--scales] \n" "\t[--key ] [--phi ] \n" "\t[--range ] [--lower ] [--upper ] \n" "\t[--temporal-coherent]\n" "\t[--verbose] [--help] \n" "See man page for more information.\n" ); } void pfstmo_reinhard02( int argc, char* argv[] ) { pfs::DOMIO pfsio; //--- default tone mapping parameters; float key = 0.18; float phi = 1.0; int num = 8; int low = 1; int high = 43; bool use_scales = false; bool temporal_coherent = false; static struct option cmdLineOptions[] = { { "help", no_argument, NULL, 'h' }, { "verbose", no_argument, NULL, 'v' }, { "scales", no_argument, NULL, 's' }, { "key", required_argument, NULL, 'k' }, { "phi", required_argument, NULL, 'p' }, { "range", required_argument, NULL, 'r' }, { "lower", required_argument, NULL, 'l' }, { "upper", required_argument, NULL, 'u' }, { "temporal-coherent", no_argument, NULL, 't' }, { NULL, 0, NULL, 0 } }; int optionIndex = 0; while( 1 ) { int c = getopt_long (argc, argv, "hvsk:p:r:l:u:t", cmdLineOptions, &optionIndex); if( c == -1 ) break; switch( c ) { case 'h': printHelp(); throw QuietException(); case 'v': verbose = true; break; case 's': use_scales = true; break; case 'k': key = (float)strtod( optarg, NULL ); if( key<=0.0f || key>1.0f ) throw pfs::Exception("key value out of range, should be <0..1>"); break; case 'p': phi = (float)strtod( optarg, NULL ); if( phi<=0.0f ) throw pfs::Exception("phi value out of range, should be >0.0"); break; case 'r': num = (int)strtod( optarg, NULL ); if( num<1 ) throw pfs::Exception("range size value out of range, should be >1"); break; case 'l': low = (int)strtod( optarg, NULL ); if( low<1 ) throw pfs::Exception("lower scale size out of range, should be >1"); break; case 'u': high = (int)strtod( optarg, NULL ); if( high<1 ) throw pfs::Exception("upper scale size out of range, should be >1"); break; case 't': temporal_coherent = true; break; case '?': throw QuietException(); case ':': throw QuietException(); } } VERBOSE_STR << "use scales: " << (use_scales ? "yes" : "no" ) << endl; VERBOSE_STR << "key value: " << key << endl; VERBOSE_STR << "phi value: " << phi << endl; if( use_scales ) { VERBOSE_STR << "number of scales: " << num << endl; VERBOSE_STR << "lower scale size: " << low << endl; VERBOSE_STR << "upper scale size: " << high << endl; #ifndef HAVE_ZFFT VERBOSE_STR << "approximate implementation of scales" << endl; #endif } while( true ) { pfs::Frame *frame = pfsio.readFrame( stdin ); if( frame == NULL ) break; // No more frames pfs::Channel *X, *Y, *Z; frame->getXYZChannels( X, Y, Z ); frame->getTags()->setString("LUMINANCE", "RELATIVE"); //--- if( Y==NULL || X==NULL || Z==NULL) throw pfs::Exception( "Missing X, Y, Z channels in the PFS stream" ); // tone mapping int w = Y->getCols(); int h = Y->getRows(); pfs::Array2DImpl* L = new pfs::Array2DImpl(w,h); tmo_reinhard02( w, h, Y->getRawData(), L->getRawData(), use_scales, key, phi, num, low, high, temporal_coherent ); for( int x=0 ; x #include #include // interpolated version of approximation (always use this one!) (:krawczyk) #define INTERPOLATED extern double **luminance; int ImageWidth, ImageHeight; double ***Pyramid; int PyramidHeight; int PyramidWidth0; void build_pyramid( double **luminance, int ImageWidth, int ImageHeight ); double V1( int x, int y, int level ); int div2( const unsigned int n ) { const int q = n/2; return(2*q < n ? q + 1 : q); } double pyramid_lookup( int x, int y, int level ) /* PRE: */ { int n, s; /* Level 0 is a special case, the value is just the image */ if (level == 0) { if ( (x < 0) || (y < 0) || (x >= ImageWidth) || (y >= ImageHeight) ) return(0.0); else return(luminance[y][x]); } /* Compute the size of the slice */ level--; n = 1 << level; s = PyramidWidth0 >> level; //x = x >> level; //y = y >> level; if ( (x < 0) || (y < 0) || (x >= s) || (y >= s) ) return(0.0); else return(Pyramid[level][y][x]); } void build_pyramid( double **luminance, int image_width, int image_height ) { int k; int x, y; int i, j; int width, height; int max_dim; int pyramid_height; double sum = 0; double a = 0.4; double b = 0.25; double c = b - a/2; double w[5]; /* Compute the "filter kernel" */ w[0] = c; w[1] = b; w[2] = a; w[3] = w[1]; w[4] = w[0]; /* Build the pyramid slices. The bottom of the pyramid is the luminace */ /* image, and is not in the Pyramid array. */ /* For simplicity, the first level is padded to a square whose side is a */ /* power of two. */ ImageWidth = image_width; ImageHeight = image_height; /* Compute the size of the Pyramid array */ max_dim = (ImageHeight > ImageWidth ? ImageHeight : ImageWidth); PyramidHeight = (int) floor(log(max_dim - 0.5)/log(2)) + 1; /* Compute the dimensions of the first level */ width = 1 << (PyramidHeight - 1); PyramidWidth0 = width; // fprintf(stderr, "max_dim %d height %d\n", max_dim, PyramidHeight); /* Allocate the outer Pyramid array */ Pyramid = (double***) calloc(PyramidHeight, sizeof(double**)); if (!Pyramid) { fprintf(stderr, "Unable to allocate pyramid array.\n"); exit(1); } /* Allocate and assign the Pyramid slices */ k = 0; while (width) { // fprintf(stderr, "level %d, width = %d\n", k, width); /* Allocate the slice */ Pyramid[k] = (double**) calloc(width, sizeof(double*)); if (!Pyramid[k]) { fprintf(stderr, "Unable to allocate pyramid array.\n"); exit(1); } for (y = 0; y < width; y++) { Pyramid[k][y] = (double*) calloc(width, sizeof(double)); if (!Pyramid[k][y]) { fprintf(stderr, "Unable to allocate pyramid array.\n"); exit(1); } } /* Compute the values in the slice */ for (y = 0; y < width; y++) { for (x = 0; x < width; x++) { sum = 0; for (i = 0; i < 5; i++) { for (j = 0; j < 5; j++) { sum += w[i]*w[j]*pyramid_lookup(2*x + i - 2, 2*y + j - 2, k); } } Pyramid[k][y][x] = sum; } } /* compute the width of the next slice */ width /= 2; k++; } } void clean_pyramid() { int k=0; int width = PyramidWidth0; while(width) { for( int y=0 ; y> level; y = y >> level; return(Pyramid[level-1][y][x]); } #else double V1( int x, int y, int level ) /* PRE: */ { int x0, y0; int l, size; double s, t; /* Level 0 is a special case, the value is just the image */ if (level == 0) return(luminance[y][x]); /* Compute the size of the slice */ l = 1 << level; x0 = x >> level; y0 = y >> level; size = PyramidWidth0 >> (level - 1); x0 = (x0 >= size ? size - 1 : x0); y0 = (y0 >= size ? size - 1 : y0); s = (double)(x - x0*l)/(double)l; t = (double)(y - y0*l)/(double)l; level--; //!! FIX: a quick fix for boundary conditions int x01,y01; x01 = (x0 == size-1 ? x0 : x0+1); y01 = (y0 == size-1 ? y0 : y0+1); return((1-s)*(1-t)*Pyramid[level][y0][x0] + s*(1-t)*Pyramid[level][y0][x01] + (1-s)*t*Pyramid[level][y01][x0] + s*t*Pyramid[level][y01][x01]); } #endif pfstools-2.2.0/src/tmo/reinhard02/tmo_reinhard02.cpp0000664000701400070140000003312414105165616020733 0ustar rkm38rkm38/** * @file tmo_reinhard02.cpp * @brief Tone map luminance channel using Reinhard02 model * * Implementation courtesy of Erik Reinhard. * * Original source code note: * Tonemap.c University of Utah / Erik Reinhard / October 2001 * * File taken from: * http://www.cs.utah.edu/~reinhard/cdrom/source.html * * Port to PFS tools library by Grzegorz Krawczyk * * $Id: tmo_reinhard02.cpp,v 1.4 2009/04/15 11:49:32 julians37 Exp $ */ #define _USE_MATH_DEFINES #include #include #include #include #include #include "pfstmo.h" #ifdef HAVE_ZFFT #include #else // if no zfft library compile approximate sollution #define APPROXIMATE #endif //--- from defines.h typedef struct { int xmax, ymax; /* image dimensions */ } CVTS; typedef double COLOR[3]; /* red, green, blue (or X,Y,Z) */ //--- end of defines.h static int width, height, scale; static COLOR **image; static double ***convolved_image; static double sigma_0, sigma_1; double **luminance; static double k = 1. / (2. * 1.4142136); static double key = 0.18; static double threshold = 0.05; static double phi = 8.; static double white = 1e20; static int scale_low = 1; static int scale_high = 43; // 1.6^8 = 43 static int range = 8; static int use_scales = 0; static int use_border = 0; static CVTS cvts = {0, 0}; static bool temporal_coherent; #ifdef APPROXIMATE extern int PyramidHeight; // set by build_pyramid, defines actual pyramid size extern double V1 (int x, int y, int level); extern void build_pyramid (double **luminance, int image_width, int image_height); extern void clean_pyramid (); #else static zomplex **filter_fft; static zomplex *image_fft, *coeff; #define V1(x,y,i) (convolved_image[i][x][y]) #endif #define SIGMA_I(i) (sigma_0 + ((double)i/(double)range)*(sigma_1 - sigma_0)) #define S_I(i) (exp (SIGMA_I(i))) #define V2(x,y,i) (V1(x,y,i+1)) #define ACTIVITY(x,y,i) ((V1(x,y,i) - V2(x,y,i)) / (((key * pow (2., phi))/(S_I(i)*S_I(i))) + V1(x,y,i))) /** * Used to achieve temporal coherence */ template class TemporalSmoothVariable { // const int hist_length = 100; T value; T getThreshold( T luminance ) { return 0.01 * luminance; } public: TemporalSmoothVariable() : value( -1 ) { } void set( T new_value ) { if( value == -1 ) value = new_value; else { T delta = new_value - value; const T threshold = getThreshold( (new_value + value)/2 ); if( delta > threshold ) delta = threshold; else if( delta < -threshold ) delta = -threshold; value += delta; } } T get() const { return value; } }; static TemporalSmoothVariable avg_luminance, max_luminance; /* * Kaiser-Bessel stuff */ static double alpha = 2.; /* Kaiser-Bessel window parameter */ static double bbeta; /* Will contain bessel (PI * alpha) */ /* * Modified zeroeth order bessel function of the first kind */ double bessel (double x) { const double f = 1e-9; int n = 1; double s = 1.; double d = 1.; double t; while (d > f * s) { t = x / (2. * n); n++; d *= t * t; s += d; } return s; } /* * Initialiser for Kaiser-Bessel computations */ void compute_bessel () { bbeta = bessel (M_PI * alpha); } /* * Kaiser-Bessel function with window length M and parameter beta = 2. * Window length M = min (width, height) / 2 */ double kaiserbessel (double x, double y, double M) { double d = 1. - ((x*x + y*y) / (M * M)); if (d <= 0.) return 0.; return bessel (M_PI * alpha * sqrt (d)) / bbeta; } /* * FFT functions */ #ifdef HAVE_ZFFT void initialise_fft (int width, int height) { coeff = zfft2di (width, height, NULL); } void compute_fft (zomplex *array, int width, int height) { zfft2d (-1, width, height, array, width, coeff); } void compute_inverse_fft (zomplex *array, int width, int height) { zfft2d (1, width, height, array, width, coeff); } // Compute Gaussian blurred images void gaussian_filter (zomplex *filter, double scale, double k ) { int x, y; double x1, y1, s; double a = 1. / (k * scale); double c = 1. / 4.; for (y = 0; y < cvts.ymax; y++) { y1 = (y >= cvts.ymax / 2) ? y - cvts.ymax : y; s = erf (a * (y1 - .5)) - erf (a * (y1 + .5)); for (x = 0; x < cvts.xmax; x++) { x1 = (x >= cvts.xmax / 2) ? x - cvts.xmax : x; filter[y*cvts.xmax + x].re = s * (erf (a * (x1 - .5)) - erf (a * (x1 + .5))) * c; filter[y*cvts.xmax + x].im = 0.; } } } void build_gaussian_fft () { int i; double length = cvts.xmax * cvts.ymax; double fft_scale = 1. / sqrt (length); filter_fft = (zomplex**) calloc (range, sizeof (zomplex*)); for (scale = 0; scale < range; scale++) { fprintf (stderr, "Computing FFT of Gaussian at scale %i (size %i x %i)%c", scale, cvts.xmax, cvts.ymax, (char)13); filter_fft[scale] = (zomplex*) calloc (length, sizeof (zomplex)); gaussian_filter (filter_fft[scale], S_I(scale), k); compute_fft (filter_fft[scale], cvts.xmax, cvts.ymax); for (i = 0; i < length; i++) { filter_fft[scale][i].re *= fft_scale; filter_fft[scale][i].im *= fft_scale; } } fprintf (stderr, "\n"); } void build_image_fft () { int i, x, y; double length = cvts.xmax * cvts.ymax; double fft_scale = 1. / sqrt (length); fprintf (stderr, "Computing image FFT\n"); image_fft = (zomplex*) calloc (length, sizeof (zomplex)); for (y = 0; y < cvts.ymax; y++) for (x = 0; x < cvts.xmax; x++) image_fft[y*cvts.xmax + x].re = luminance[y][x]; compute_fft (image_fft, cvts.xmax, cvts.ymax); for (i = 0; i < length; i++) { image_fft[i].re *= fft_scale; image_fft[i].im *= fft_scale; } } void convolve_filter (int scale, zomplex *convolution_fft) { int i, x, y; for (i = 0; i < cvts.xmax * cvts.ymax; i++) { convolution_fft[i].re = image_fft[i].re * filter_fft[scale][i].re - image_fft[i].im * filter_fft[scale][i].im; convolution_fft[i].im = image_fft[i].re * filter_fft[scale][i].im + image_fft[i].im * filter_fft[scale][i].re; } compute_inverse_fft (convolution_fft, cvts.xmax, cvts.ymax); i = 0; for (y = 0; y < cvts.ymax; y++) for (x = 0; x < cvts.xmax; x++) convolved_image[scale][x][y] = convolution_fft[i++].re; } void compute_fourier_convolution () { int x; zomplex *convolution_fft; initialise_fft (cvts.xmax, cvts.ymax); build_image_fft (); build_gaussian_fft (); convolved_image = (double ***) malloc (range * sizeof (double **)); convolution_fft = (zomplex *) calloc (cvts.xmax * cvts.ymax, sizeof (zomplex)); for (scale = 0; scale < range; scale++) { fprintf (stderr, "Computing convolved image at scale %i%c", scale, (char)13); convolved_image[scale] = (double **) malloc (cvts.xmax * sizeof (double *)); for (x = 0; x < cvts.xmax; x++) convolved_image[scale][x] = (double *) malloc (cvts.ymax * sizeof (double)); convolve_filter (scale, convolution_fft); free (filter_fft[scale]); } fprintf (stderr, "\n"); free (convolution_fft); free (image_fft); } #endif // #ifdef HAVE_ZFFT /* * Tonemapping routines */ double get_maxvalue () { double max = 0.; int x, y; for (y = 0; y < cvts.ymax; y++) for (x = 0; x < cvts.xmax; x++) max = (max < image[y][x][0]) ? image[y][x][0] : max; return max; } void tonemap_image () { double Lmax2; int x, y; int scale, prefscale; if (white < 1e20) Lmax2 = white * white; else { if( temporal_coherent ) { max_luminance.set( get_maxvalue() ); Lmax2 = max_luminance.get(); } else Lmax2 = get_maxvalue(); Lmax2 *= Lmax2; } for (y = 0; y < cvts.ymax; y++) for (x = 0; x < cvts.xmax; x++) { if (use_scales) { prefscale = range - 1; for (scale = 0; scale < range - 1; scale++) if ( scale >= PyramidHeight || fabs (ACTIVITY(x,y,scale)) > threshold) { prefscale = scale; break; } image[y][x][0] /= 1. + V1(x,y,prefscale); } else image[y][x][0] = image[y][x][0] * (1. + (image[y][x][0] / Lmax2)) / (1. + image[y][x][0]); // image[y][x][0] /= (1. + image[y][x][0]); } } /* * Miscellaneous functions */ void clamp_image () { int x, y; for (y = 0; y < cvts.ymax; y++) for (x = 0; x < cvts.xmax; x++) { image[y][x][0] = (image[y][x][0] > 1.) ? 1. : image[y][x][0]; image[y][x][1] = (image[y][x][1] > 1.) ? 1. : image[y][x][1]; image[y][x][2] = (image[y][x][2] > 1.) ? 1. : image[y][x][2]; } } double log_average () { int x, y; double sum = 0.; for (y = 0; y < cvts.ymax; y++) for (x = 0; x < cvts.xmax; x++) sum += log (0.00001 + luminance[y][x]); return exp (sum / (double)(cvts.xmax * cvts.ymax)); } void scale_to_midtone () { int x, y, u, v, d; double factor; double scale_factor; double low_tone = key / 3.; int border_size = (cvts.xmax < cvts.ymax) ? int(cvts.xmax / 5.) : int(cvts.ymax / 5.); int hw = cvts.xmax >> 1; int hh = cvts.ymax >> 1; double avg; if( temporal_coherent ) { avg_luminance.set( log_average() ); avg = avg_luminance.get(); } else avg = log_average(); scale_factor = 1.0 / avg; for (y = 0; y < cvts.ymax; y++) for (x = 0; x < cvts.xmax; x++) { if (use_border) { u = (x > hw) ? cvts.xmax - x : x; v = (y > hh) ? cvts.ymax - y : y; d = (u < v) ? u : v; factor = (d < border_size) ? (key - low_tone) * kaiserbessel (border_size - d, 0, border_size) + low_tone : key; } else factor = key; image[y][x][0] *= scale_factor * factor; luminance[y][x] *= scale_factor * factor; } } void copy_luminance () { int x, y; for (x = 0; x < cvts.xmax; x++) for (y = 0; y < cvts.ymax; y++) luminance[y][x] = image[y][x][0]; } /* * Memory allocation */ void allocate_memory () { int y; luminance = (double **) malloc (cvts.ymax * sizeof (double *)); image = (COLOR **) malloc (cvts.ymax * sizeof (COLOR *)); for (y = 0; y < cvts.ymax; y++) { luminance[y] = (double *) malloc (cvts.xmax * sizeof (double)); image[y] = (COLOR *) malloc (cvts.xmax * sizeof (COLOR)); } } void deallocate_memory () { int y; for (y = 0; y < cvts.ymax; y++) { free(luminance[y]); free(image[y]); } free( luminance ); free( image ); } void dynamic_range () { int x, y; double minval = 1e20; double maxval = -1e20; for (x = 0; x < cvts.xmax; x++) for (y = 0; y < cvts.ymax; y++) { if ((luminance[y][x] < minval) && (luminance[y][x] > 0.0)) minval = luminance[y][x]; if (luminance[y][x] > maxval) maxval = luminance[y][x]; } fprintf (stderr, "\tRange of values = %9.8f - %9.8f\n", minval, maxval); fprintf (stderr, "\tDynamic range = %i:1\n", (int)(maxval/minval)); } void print_parameter_settings () { fprintf (stderr, "\tImage size = %i %i\n", cvts.xmax, cvts.ymax); fprintf (stderr, "\tLowest scale = %i pixels\t\t(-low )\n", scale_low); fprintf (stderr, "\tHighest scale = %i pixels\t\t(-high )\n", scale_high); fprintf (stderr, "\tNumber of scales = %i\t\t\t(-num )\n", range); fprintf (stderr, "\tScale spacing = %f\n", S_I(1) / S_I(0)); fprintf (stderr, "\tKey value = %f\t\t(-key )\n", key); fprintf (stderr, "\tWhite value = %f\t\t(-white )\n", white); fprintf (stderr, "\tPhi = %f\t\t(-phi )\n", phi); fprintf (stderr, "\tThreshold = %f\t\t(-threshold )\n", threshold); dynamic_range (); } /* * @brief Photographic tone-reproduction * * @param Y input luminance * @param L output tonemapped intensities * @param use_scales true: local version, false: global version of TMO * @param key maps log average luminance to this value (default: 0.18) * @param phi sharpening parameter (defaults to 1 - no sharpening) * @param num number of scales to use in computation (default: 8) * @param low size in pixels of smallest scale (should be kept at 1) * @param high size in pixels of largest scale (default 1.6^8 = 43) */ void tmo_reinhard02( unsigned int width, unsigned int height, const float *nY, float *nL, bool use_scales, float key, float phi, int num, int low, int high, bool temporal_coherent ) { const pfstmo::Array2D* Y = new pfstmo::Array2D(width, height, const_cast(nY)); pfstmo::Array2D* L = new pfstmo::Array2D(width, height, nL); int x,y; ::key = key; ::phi = phi; ::range = num; ::scale_low = low; ::scale_high = high; ::use_scales = (use_scales) ? 1 : 0; ::temporal_coherent = temporal_coherent; cvts.xmax = Y->getCols(); cvts.ymax = Y->getRows(); sigma_0 = log (scale_low); sigma_1 = log (scale_high); compute_bessel(); allocate_memory (); // reading image for( y=0 ; y] [\fB--display-size\fR=<\fIsize-spec\fR>] [\fB--color-saturation\fR <\fIfloat\fR>] [\fB--contrast-enhancement\fR <\fIfloat\fR>] [\fB--white-y\fR=<\fIfloat\fR>] [\fB--fps\fR=<\fIframes-per-second\fR>] [\fB--output-tone-curve\fR=<\fIfile name\fR>] [\fB--verbose\fR] [\fB--help\fR] .SH DESCRIPTION This command applies the display adaptive tone mapping, which attempts to preserve contrast of an input (HDR) image as close as possible given the characteristic of an output display. Use this tone mapping operator if you want to preserve original image appearance, or slightly enhance contrast (-\fBe\fR option) while maintaining the natural look of images. The operator can also compensate for ambient light reflections on a screen, and for varying dynamic range and brightness of a display. The operator is suitable for video sequences as it prevents high-frequency changes in tone-curve between consecutive frames, which would result in flickering. Note that the temporal filtering is always active and there is no need to specify an argument to switch it on. .HP .PD 0 More details can be found in: .IP Rafal Mantiuk, Scott Daly and Louis Kerofsky. .IP Display Adaptive Tone Mapping. .IP In: ACM Transactions on Graphics 27 (3), 2008. .IP http://www.mpi-inf.mpg.de/resources/hdr/datmo/ .PD .PP If you find this TMO useful in your research project, please cite the paper above. .HP .PD 0 This operator also employs color correction mechanism from: .IP Radoslaw Mantiuk, Rafal Mantiuk, Anna Tomaszewska, Wolfgang Heidrich. .IP Color Correction for Tone Mapping. .IP In: Computer Graphics Forum (Proc. of EUROGRAPHICS'09), 28(2), 2009. .IP http://zgk.wi.ps.pl/color_correction/ .PD .PP The result of this TMO does not require gamma correction. .SH OPTIONS .TP \fB--display-function\fR <\fIdf-spec\fR>, \fB-d\fR <\fIdf-spec\fR> To adapt tone-mapping to different displays, this operator must be provided a display function. The display function describes how output luminance of a display changes with pixel values. If no parameter is given, the command assumes \fB-df\ pd=lcd\fR (see \fIPre-defined display\fR below). There are several ways to specify the display function: .TP \fIGamma-gain-black-ambient display model\fR .IP g=:l=:b=:k=:a=[:n=] .IP Gamma-gain-black-ambient model can approximate a range of displays and is a compact way to specify a display function. It assumes that a display function has the following form: .IP L_d(I) = (l-b)*I^gamma + b + k/pi*a .IP The parameters are as follows: .RS .PD 0 .TP 5 \fBg\fR - gamma or exponent of a display function (default 2.2, usually from 1.8 to 2.8) .TP 5 \fBl\fR - peak luminance of a display in cd/m^2 (default 100, from 80 for CRTs to 500 or more for newer displays) .TP 5 \fBb\fR - black level, which is luminance of a black pixel when the display is on (default 1, usually from 0.3 to 1 cd/m^2) .TP 5 \fBk\fR - reflectivity of a screen (assuming that it is diffuse) (default 0.01, usually about 0.01 (1%) for LCD displays, more for CRTs) .TP 5 \fBa\fR - ambient illumination in lux. Typical values are: .RS .IP 50\ lux Family living room (dim, \fBdefault\fR) .IP 400\ lux A brightly lit office .IP 32000\ lux Sunlight on an average day (min.) .IP 100000\ lux Sunlight on an average day (max.) .RE .RE .PD .TP \fIPre-defined display\fR .IP \fBpd\fR=\fI\fI .IP Use pre-defined display type. This options are for convenience only and they do not mean to accurately model the response of a particular display. The following \fIdisplay type\fRs are recognized: .RS .TP \fBlcd_office\fR (g=2.2, l=100, b=0.8, k=0.01, a=400 ) lcd set to "office" mode seen in bright environment .PD 0 .TP \fBlcd\fR (g=2.2, l=200, b=0.8, k=0.01, a=60 ) typical lcd seen in dim environment (\fBdefault\fR) .TP \fBlcd_bright\fR (g=2.6, l=500, b=0.5, k=0.01, a=10 ) newer LCD TV seen in dark environment .TP \fBcrt\fR (g=2.2, l=80, b=1, k=0.02, a=60 ) CRT monitor seen in dim environment .PD .RE .IP The parameters in the parenthesis are the same as for the gamma-gain-black-ambient model explained above. .TP \fILookup-table\fR .IP \fBlut\fR=\fI\fI .IP This is the most accurate specification of the display response function, but requires measuring it with a luminance meter. The lookup table should account also for ambient light, so that it is recommended to use the luminance meter that can measure screen luminance from a distance, such as Minolta LS-100 (as opposed to those that use rubber tube touching a display that eliminates the influence of ambient light). The must be a comma-separated text file in a format (CSV) with two columns: first column represents pixel values (from 0.0 to 1.0) and the second physical luminance in cd/m^2. Both the pixel value and the luminance should increase in each raw. .TP \fB--display-size\fR=<\fIsize-spec\fR>, \fB-s\fR=<\fIsize_spec\fR> Specifies how large the image appears to a viewer and what is the viewing distance. If no parameter is given, \fB-s\ ppd=30\fR is assumed. Since this tone-mapper is global, display size has moderate effect on the resulting images and thus skipping this parameter should not do much harm. There are two ways to specify image size: .RS .PD 0 .TP \fBvres\fR=:\fBvd\fR=[:\fBd\fR=<\fImeters\fR>] .RS .IP \fBvres\fR - screen's vertical resolution in lines, for example 1024. .IP \fBvd\fR - viewing distance specified as multiplies of screen height. For example if the display is seen from 0.5m and the height of its screen is 25cm, \fBvd\fR=2. .IP \fBd\fR - (optional) viewing distance in meters. This is to account for lower eye's sensitivity for larger viewing distances (although the effect is negligible). By default \fB-d\fR=0.5 is assumed. .RE .TP \fBppd\fR=<\fIpixels_per_visual_degree\fR>[:\fBd\fR=<\fImeters\fR>] .RS .IP \fBppd\fR - how many pixels spans one visual degree. .IP \fBd\fR - (optional) viewing distance in meters. This is to account for lower eye's sensitivity for larger viewing distances (although the effect is negligible). By default \fB-d\fR=0.5 is assumed. .RE .RE .PD .TP \fB--color-saturation\fR <\fIfloat\fR>, \fB-c\fR <\fIfloat\fR> Decrease or increase color saturation after tone mapping. Default value \fB-c=1\fR attempts to preserve color appearance of the original image. Use values >1 to increase and <1 to decrease color saturation. .TP \fB--contrast-enhancement\fR <\fIfloat\fR>, \fB-e\fR <\fIfloat\fR> By default this tone-mapper attempts to preserve contrast of an input image (\fB-e=1\fR). This parameter controls whether the contrast of an input image should be enhanced before tone-mapping. For example \fB-e=1.15\fR boosts contrast by 15%. Note that if a target display does not offer sufficient dynamic range, contrast may be enhanced only for selected tone-values (those that dominate in an image) or not enhanced at all. .TP \fB--white-y\fR=<\fIfloat\fR>, \fB-y\fR=<\fIfloat\fR> Tells the tone-mapper what luminance level in the input image should be mapped to the maximum luminance of a display. Since HDR images contain only relative luminance information, tone-mapper does not know how bright should be the scene. This option is meant to fix this problem by providing tone-mapper with the information what luminance level in an input image should be perceived as a diffuse white surface. Default is \fInone\fR, which means that no such mapping will be enforced and tone-mapper is free to find an optimal brightness for a given image. This is a recommended setting for HDR images. Setting \fB--white-y\fR could be necessary for dark scenes, which could be made too bright by the tone-mapper. The value of this parameter can be also passed in pfsstream as a tag \fIWHITE_Y\fR. pfstools 1.7 and newer sets set this tag automatically for LDR images. The command line option overrides the value of the pfstream tag. .TP \fB--fps\fR=<\fIframes-per-second\fR>, \fB-f\fR=<\fIframes-per-second\fR> Set the frame rate of the input sequence. Default is 25. Currently only 3 values are supported: 25, 30 and 60. This parameter controls temporal filter that makes sure the resulting sequence is coherent in time. This reduces the likelihood of a visible flicker. .TP \fB--output-tone-curve\fR=<\fIfile name\fR>, \fB-o\fR=<\fIfile name\fR> Write tone-curves to a text file. This option is mainly for debugging purposes, but can be used to visualize computed tone-curves. The tone-curve data is stored in a comma separated text file (CSV), consisting of three columns: frame number, log10 of input luminance factor, log10 of the resulting display luminance, and the pixel value (0-1). .TP \fB--verbose\fR, \fB-v\fR Print additional information during program execution. .TP \fB--quiet\fR, \fB-q\fR Do not display progress report. .TP \fB--help\fR, \fB-h\fR Print list of commandline options. .SH EXAMPLES .TP pfsin memorial.hdr | pfstmo_mantiuk08 -d pd=crt | pfsout memorial.png .IP Tone map memorial image for a CRT display and store the result in the PNG format. .TP pfsin memorial.hdr | pfstmo_mantiuk08 -d g=2.6:l=500:b=0.5:k=0.01:a=10 | pfsview .IP Tone map memorial image for a display that has a 2.2 gamma, the peak luminance of 500 cd/m^2, the black level of 0.5 cd/m^2, the panel reflectivity of 1% (0.01) and is seen under the illumination of 10 lux. .TP pfsin bridge.jpg --linear | pfsclamp --min 0.007 | pfstmo_mantiuk08 -v | pfsview .IP Enhance the low-dynamic range image 'bridge' and view the result. pfsclamp command reduces noise for low code values. .HP .PD 0 pfsin frame%05d.exr | pfstmo_mantiuk08 -d pd=lcd_bright --fps 30 | pfsout out_frame%04d.png .PD .IP Tone-map video sequence at 30 frame-per-second frame rate. .TP pfsin *.exr | pfstmo_mantiuk08 | pfsview .IP Tone-map and display *.exr HDR images in the current directory. .TP pfsin *.exr | pfstmo_mantiuk06 | pfsgamma -g 0.8 | pfstmo_mantiuk08 | pfsview .IP It is possible to stack a TMO that sharpens images (pfstmo_mantiuk06) with the contrast preserving TMO (pfstmo_mantiuk08) to get new interesting results. .SH "SEE ALSO" .BR pfsin (1) .BR pfsout (1) .BR pfsview (1) .SH BUGS Please report bugs and comments to the pfstools discussion group (http://groups.google.com/group/pfstools). pfstools-2.2.0/src/tmo/mantiuk08/display_function.cpp0000664000701400070140000001657014105165616021360 0ustar rkm38rkm38/** * @brief Display Adaptive TMO * * From: * Rafal Mantiuk, Scott Daly, Louis Kerofsky. * Display Adaptive Tone Mapping. * To appear in: ACM Transactions on Graphics (Proc. of SIGGRAPH'08) 27 (3) * http://www.mpi-inf.mpg.de/resources/hdr/datmo/ * * This file is a part of PFSTMO package. * ---------------------------------------------------------------------- * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Rafal Mantiuk, * * $Id: display_function.cpp,v 1.4 2010/07/13 16:30:13 rafm Exp $ */ #include #include #include #include #include #include "display_function.h" static void removeCommandLineArg( int &argc, char* argv[], int firstArgToRemove, int numArgsToRemove ); // ========== GGBA Display Function ============== DisplayFunctionGGBA::DisplayFunctionGGBA( float gamma, float L_max, float L_black, float E_amb, float screen_refl ) { init( gamma, L_max, L_black, E_amb, screen_refl ); } void DisplayFunctionGGBA::init( float gamma, float L_max, float L_black, float E_amb, float screen_refl ) { this->gamma = gamma; this->L_max = L_max; this->L_black = L_black; this->E_amb = E_amb; this->screen_refl = screen_refl; this->L_offset = L_black + screen_refl/M_PI*E_amb; } DisplayFunctionGGBA::DisplayFunctionGGBA( const char *predefined ) { if( !strcasecmp( predefined, "lcd_office" ) ) { init( 2.2f, 100, 0.8, 400, 0.01 ); } else if( !strcasecmp( predefined, "lcd" ) ) { init( 2.2f, 200, 0.8, 60, 0.01 ); } else if( !strcasecmp( predefined, "lcd_bright" ) ) { init( 2.6f, 500, 0.5, 10, 0.01 ); } else if( !strcasecmp( predefined, "crt" ) ) { init( 2.2f, 80, 1, 60, 0.02 ); } else { throw pfs::Exception( "Unknown display type. Recognized types: lcd_office, lcd, lcd_bright, crt." ); } } void DisplayFunctionGGBA::print( FILE *fh ) { fprintf( fh, "Display function: gamma-gain-black-ambient\n" ); fprintf( fh, " gamma = %g\t L_max = %g\t L_black = %g\n", (double)gamma, (double)L_max, (double)L_black ); fprintf( fh, " E_amb = %g\t k = %g\n", (double)E_amb, (double)screen_refl ); } float DisplayFunctionGGBA::inv_display( float L ) { if( L < L_offset ) L = L_offset; if( L > (L_offset+L_max) ) L = L_offset + L_max; return powf( (L - L_offset)/(L_max-L_black), 1/gamma ); } float DisplayFunctionGGBA::display( float pix ) { assert( pix >= 0 && pix <= 1 ); return pow( pix, gamma ) * (L_max-L_black) + L_offset; } // ========== LUT Display Function ============== static const size_t max_lut_size = 4096; DisplayFunctionLUT::DisplayFunctionLUT( const char *file_name ) : pix_lut( NULL ), L_lut( NULL ) { FILE *fh = fopen( file_name, "r" ); if( fh == NULL ) throw pfs::Exception( "Cannot open lookup-table file" ); L_lut = new float[max_lut_size]; pix_lut = new float[max_lut_size]; const size_t max_line = 100; char buf[max_line]; int i = 0; while( fgets( buf, max_line, fh ) != NULL ) { float p_buf, L_buf; if( sscanf( buf, "%f%*[ ,;]%f\n", &p_buf, &L_buf ) != 2 ) continue; if( p_buf < 0 || p_buf > 1 ) throw pfs::Exception( "Improper LUT: pixel values must be from 0 to 1" ); if( L_buf <= 0 ) throw pfs::Exception( "Improper LUT: luminance must be greater than 0" ); L_lut[i] = log10( L_buf ); pix_lut[i] = p_buf; i++; if( i >= max_lut_size ) throw pfs::Exception( "LUT too large (more than 4096 entries)" ); } lut_size = i; if( pix_lut[0] != 0 || pix_lut[lut_size-1]!= 1 ) throw pfs::Exception( "The first and last LUT entries for pixel value should be 0 and 1" ); } DisplayFunctionLUT::~DisplayFunctionLUT() { delete []pix_lut; delete []L_lut; } inline float bin_search_interp( float x, const float *lut_x, const float *lut_y, const int lutSize ) { if( x <= lut_x[0] ) return lut_y[0]; if( x >= lut_x[lutSize-1] ) return lut_y[lutSize-1]; size_t l = 0, r = lutSize; while( true ) { size_t m = (l+r)/2; if( m == l ) break; if( x < lut_x[m] ) r = m; else l = m; } return lut_y[l] + (lut_y[l+1]-lut_y[l])*(x-lut_x[l])/(lut_x[l+1]-lut_x[l]); } float DisplayFunctionLUT::inv_display( float L ) { return bin_search_interp( log10( L ), L_lut, pix_lut, lut_size ); } float DisplayFunctionLUT::display( float pix ) { return pow( 10, bin_search_interp( pix, pix_lut, L_lut, lut_size ) ); } void DisplayFunctionLUT::print( FILE *fh ) { fprintf( fh, "Display function: lookup-table\n" ); fprintf( fh, " L_min = %g \tL_max = %g\n", (double)pow( 10, L_lut[0] ), (double)pow( 10, L_lut[lut_size-1] ) ); } // ========== Command line parsing ============== DisplayFunction *createDisplayFunctionFromArgs( int &argc, char* argv[] ) { DisplayFunction *df = 0; for( int i=1 ; i= argc ) throw pfs::Exception( "missing display function specification" ); float gamma = 2.2f, L_max = 100.f, L_black = 1.f, k = 0.01, E_amb = 50; bool GGBA_model = true; char *token; token = strtok( argv[i+1], ":" ); while( token != NULL ) { if( !strncmp( token, "pd=", 3 ) ) { df = new DisplayFunctionGGBA( token+3 ); GGBA_model = false; break; } else if( !strncmp( token, "lut=", 4 ) ) { df = new DisplayFunctionLUT( token+4 ); GGBA_model = false; break; } else if( !strncmp( token, "g=", 2 ) ) { gamma = strtod( token+2, NULL ); } else if( !strncmp( token, "l=", 2 ) ) { L_max = strtod( token+2, NULL ); } else if( !strncmp( token, "b=", 2 ) ) { L_black = strtod( token+2, NULL ); } else if( !strncmp( token, "k=", 2 ) ) { k = strtod( token+2, NULL ); } else if( !strncmp( token, "a=", 2 ) ) { E_amb = strtod( token+2, NULL ); } else { throw pfs::Exception( "Bad display type specification" ); } token = strtok( NULL, ":" ); } if( GGBA_model ) df = new DisplayFunctionGGBA( gamma, L_max, L_black, E_amb, k ); removeCommandLineArg( argc, argv, i, 2 ); break; } } return df; } static void removeCommandLineArg( int &argc, char* argv[], int firstArgToRemove, int numArgsToRemove ) { assert( firstArgToRemove+numArgsToRemove <= argc ); if( argc-firstArgToRemove-numArgsToRemove > 0 ) { for( int i = firstArgToRemove; i < argc-numArgsToRemove; i++ ) argv[i] = argv[i+numArgsToRemove]; } argc -= numArgsToRemove; } pfstools-2.2.0/src/tmo/mantiuk08/CMakeLists.txt0000664000701400070140000000115614105165616020034 0ustar rkm38rkm38include_directories ("${PROJECT_BINARY_DIR}/" "${PROJECT_SOURCE_DIR}/src/pfs" "${PROJECT_SOURCE_DIR}/src/tmo/pfstmo" "${GSL_INCLUDE_DIR}") if( NOT HAS_GETOPT ) include_directories ("${GETOPT_INCLUDE}") endif( NOT HAS_GETOPT ) link_directories("${PROJECT_SOURCE_DIR}/src/pfs") set(TRG pfstmo_mantiuk08) add_executable(${TRG} ${TRG}.cpp display_adaptive_tmo.cpp display_function.cpp display_size.cpp cqp/cqpminimizer.cpp cqp/initial_point.cpp cqp/mg_pdip.cpp "${GETOPT_OBJECT}") target_link_libraries(${TRG} pfs ${GSL_LIBRARIES}) install (TARGETS ${TRG} DESTINATION bin) install (FILES ${TRG}.1 DESTINATION ${MAN_DIR}) pfstools-2.2.0/src/tmo/mantiuk08/display_size.h0000664000701400070140000000374114105165616020146 0ustar rkm38rkm38/** * @brief Display Adaptive TMO * * From: * Rafal Mantiuk, Scott Daly, Louis Kerofsky. * Display Adaptive Tone Mapping. * To appear in: ACM Transactions on Graphics (Proc. of SIGGRAPH'08) 27 (3) * http://www.mpi-inf.mpg.de/resources/hdr/datmo/ * * This file is a part of PFSTMO package. * ---------------------------------------------------------------------- * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Rafal Mantiuk, * * $Id: display_size.h,v 1.2 2008/06/16 18:42:58 rafm Exp $ */ #ifndef DISPLAY_SIZE_H #define DISPLAY_SIZE_H #include class DisplaySize { float view_d; float ppd; public: /** * @param vres vertical screen resolution in pixels * @param vd_screen_h viewing distance as the multiplies of screen height (e.g. 2) * @param vd_meters viewing distance in meters */ DisplaySize( int vres, float vd_screen_h, float vd_meters = 0.5 ); /** * @param ppd number of pixels per one visual degree (e.g. 30) * @param vd_meters viewing distance in meters */ DisplaySize( float ppd, float vd_meters = 0.5 ); void print( FILE *fh ); float getPixPerDeg(); float getViewD(); }; DisplaySize *createDisplaySizeFromArgs( int &argc, char* argv[] ); #endif pfstools-2.2.0/src/tmo/mantiuk08/display_size.cpp0000664000701400070140000000721014105165616020474 0ustar rkm38rkm38/** * @brief Display Adaptive TMO * * From: * Rafal Mantiuk, Scott Daly, Louis Kerofsky. * Display Adaptive Tone Mapping. * To appear in: ACM Transactions on Graphics (Proc. of SIGGRAPH'08) 27 (3) * http://www.mpi-inf.mpg.de/resources/hdr/datmo/ * * This file is a part of PFSTMO package. * ---------------------------------------------------------------------- * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Rafal Mantiuk, * * $Id: display_size.cpp,v 1.3 2008/06/16 18:42:58 rafm Exp $ */ #include "display_size.h" #include #include #include #include static void removeCommandLineArg( int &argc, char* argv[], int firstArgToRemove, int numArgsToRemove ); DisplaySize::DisplaySize( int vres, float vd_screen_h, float vd_meters ) : view_d(vd_meters) { ppd = vres * M_PI / (360* atan(0.5/vd_screen_h)); } DisplaySize::DisplaySize( float ppd, float vd_meters ) : ppd( ppd ), view_d(vd_meters) { } void DisplaySize::print( FILE *fh ) { fprintf( fh, "Display size paramaters:\n" ); fprintf( fh, " pixels per visual degree = %g\n", (double)getPixPerDeg() ); fprintf( fh, " viewing distance = %g [meters]\n", (double)getViewD() ); } float DisplaySize::getPixPerDeg() { return ppd; } float DisplaySize::getViewD() { return view_d; } // ========== Command line parsing ============== DisplaySize *createDisplaySizeFromArgs( int &argc, char* argv[] ) { DisplaySize *ds = 0; for( int i=1 ; i= argc ) throw pfs::Exception( "missing display size specification" ); float vres = 1024, vd = 2, d = 0.5, ppd = -1; char *token; token = strtok( argv[i+1], ":" ); while( token != NULL ) { if( !strncmp( token, "vres=", 5 ) ) { vres = strtod( token+5, NULL ); } else if( !strncmp( token, "vd=", 3 ) ) { vd = strtod( token+3, NULL ); } else if( !strncmp( token, "d=", 2 ) ) { d = strtod( token+2, NULL ); } else if( !strncmp( token, "ppd=", 4 ) ) { ppd = strtod( token+4, NULL ); } else { throw pfs::Exception( "Bad display size specification" ); } token = strtok( NULL, ":" ); } if( ppd != -1 ) ds = new DisplaySize( ppd, d ); else ds = new DisplaySize( vres, vd, d ); removeCommandLineArg( argc, argv, i, 2 ); break; } } return ds; } static void removeCommandLineArg( int &argc, char* argv[], int firstArgToRemove, int numArgsToRemove ) { assert( firstArgToRemove+numArgsToRemove <= argc ); if( argc-firstArgToRemove-numArgsToRemove > 0 ) { for( int i = firstArgToRemove; i < argc-numArgsToRemove; i++ ) argv[i] = argv[i+numArgsToRemove]; } argc -= numArgsToRemove; } pfstools-2.2.0/src/tmo/mantiuk08/cqp/0002775000701400070140000000000014105165616016056 5ustar rkm38rkm38pfstools-2.2.0/src/tmo/mantiuk08/cqp/gsl_cqp.h0000664000701400070140000000745414105165616017667 0ustar rkm38rkm38/** * @brief Convex Quadratic Programming library * * From: * http://ra.uni-trier.de/~huebner/software.html * * ---------------------------------------------------------------------- * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Ewgenij Hübner * * $$ */ #ifndef GSL_CQP_H #define GSL_CQP_H #include #include #ifdef __cplusplus # define __BEGIN_DECLS extern "C" { # define __END_DECLS } #else # define __BEGIN_DECLS /* empty */ # define __END_DECLS /* empty */ #endif __BEGIN_DECLS typedef struct { /* objective function: 0.5*(x^t)Qx+(q^t)x */ gsl_matrix * Q; gsl_vector * q; /* constraints: Ax=b; Cx>=d */ const gsl_matrix * A; const gsl_vector * b; gsl_matrix * C; gsl_vector * d; } gsl_cqp_data; typedef struct { const char *name; size_t size; int (*alloc) (void *state, size_t n, size_t me, size_t mi); int (*set) (void *state, const gsl_cqp_data *cqp, gsl_vector *x, gsl_vector *y, gsl_vector *z, double *gap, double *residuals_norm, double *data_norm, double *inf_barrier, double *inf_barrier_min); int (*iterate) (void *state, const gsl_cqp_data * cqp, gsl_vector *x, gsl_vector *y, gsl_vector *z, double *gap, double *residuals_norm, double *inf_barrier, double *inf_barrier_min); /* int (*restart) (void *state); */ void (*free) (void *state); } gsl_cqpminimizer_type; typedef struct { const gsl_cqpminimizer_type * type; gsl_cqp_data * cqp; gsl_vector * x; /* Lagrange-multipliers */ gsl_vector * y; /*corresponding to Ax=b */ gsl_vector * z; /*corresponding to CX>=d */ double gap; double residuals_norm; double data_norm; double quantity_of_infeasibility; double quantity_of_infeasibility_min; void *state; } gsl_cqpminimizer; gsl_cqpminimizer * gsl_cqpminimizer_alloc(const gsl_cqpminimizer_type *T, size_t n, size_t me, size_t mi); /* int gsl_cqpminimizer_set (gsl_cqpminimizer * minimizer, gsl_cqp_problem * cqp); */ int gsl_cqpminimizer_set (gsl_cqpminimizer * minimizer, gsl_cqp_data * cqp); void gsl_cqpminimizer_free(gsl_cqpminimizer *minimizer); const char * gsl_cqpminimizer_name (const gsl_cqpminimizer * minimizer); int gsl_cqpminimizer_iterate(gsl_cqpminimizer *minimizer); /* int gsl_cqpminimizer_restart(gsl_cqpminimizer *minimizer); */ gsl_vector * gsl_cqpminimizer_x (gsl_cqpminimizer * minimizer); gsl_vector * gsl_cqpminimizer_lm_eq (gsl_cqpminimizer * minimizer); gsl_vector * gsl_cqpminimizer_lm_ineq (gsl_cqpminimizer * minimizer); double gsl_cqpminimizer_f (gsl_cqpminimizer * minimizer); double gsl_cqpminimizer_gap (gsl_cqpminimizer *minimizer); double gsl_cqpminimizer_residuals_norm (gsl_cqpminimizer *minimizer); int gsl_cqpminimizer_test_convergence(gsl_cqpminimizer * minimizer, double eps_gap, double eps_residual); int gsl_cqp_minimizer_test_infeasibility(gsl_cqpminimizer * minimizer, double eps_infeasible); double gsl_cqpminimizer_minimum (gsl_cqpminimizer * minimizer); GSL_VAR const gsl_cqpminimizer_type *gsl_cqpminimizer_mg_pdip; /*GSL_VAR const gsl_cqpminimizer_type *gsl_cqpminimizer_pdip_mpc_eqc;*/ __END_DECLS #endif pfstools-2.2.0/src/tmo/mantiuk08/cqp/mg_pdip.cpp0000664000701400070140000005040414105165616020202 0ustar rkm38rkm38/** * @brief Convex Quadratic Programming library * * From: * http://ra.uni-trier.de/~huebner/software.html * * ---------------------------------------------------------------------- * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Ewgenij Hübner * * $$ */ #include #include #include #include #include #include #include "gsl_cqp.h" #include "initial_point.h" static void print_vectors(const gsl_vector*,const gsl_vector*,const gsl_vector*,const gsl_vector*); static double compute_gap_infeasible_points(const gsl_cqp_data * cqp, const gsl_vector *x, const gsl_vector *y, const gsl_vector *z); static int compute_residuals(const gsl_cqp_data * cqp, const gsl_vector *x, const gsl_vector *y, const gsl_vector *z, const gsl_vector *s, gsl_vector *r); static int build_kkt_matrix(const gsl_cqp_data * cqp, const gsl_vector *z, const gsl_vector *s, gsl_matrix *kkt_matrix); double step_length(const gsl_vector *s, const gsl_vector *z, const gsl_vector *delta_s, const gsl_vector *delta_z); double gsl_vector_max_norm(const gsl_vector *v); double gsl_matrix_max_norm(const gsl_matrix *M); typedef struct { size_t n; /* dimension of the problem*/ size_t me; /* number of the equality constraints*/ size_t mi; /* number of the inequality constraints */ gsl_vector *s; /* Slack vector for the constrain: Cx+s = d */ gsl_matrix *kkt_matrix; gsl_vector *r; /* the vector of the right-hand side in the kkt-system: r=(r_Q,r_A,r_C) */ double tau; /* a constant for the Mehrotra's heuristic */ /* gondzio's parameters */ size_t k_max; /*maximal number of corrections */ double beta_min, beta_max; /* relatibe threhold values for outlier compl. products */ double delta_alpha; /* the required increas of stepsize */ double gamma; /* the minimum acceptable increase of stepsize */ double data_norm; /* norm of the problem data */ } mg_pdip_state; static int mg_pdip_alloc (void *vstate, size_t n, size_t me, size_t mi) { mg_pdip_state * state = (mg_pdip_state *) vstate; state->s = gsl_vector_alloc(mi); if(state->s == 0) { GSL_ERROR_VAL ("failed to initialize space for the slack vector s", GSL_ENOMEM, 0); } state->kkt_matrix = gsl_matrix_calloc(n+me+mi, n+me+mi); if(state->kkt_matrix == 0) { gsl_vector_free(state->s); GSL_ERROR_VAL ("failed to initialize space for the KKT-Matrix", GSL_ENOMEM, 0); } state->r = gsl_vector_alloc(state->kkt_matrix->size1); if(state->r == 0) { gsl_matrix_free(state->kkt_matrix); gsl_vector_free(state->s); GSL_ERROR_VAL ("failed to initialize space for right-hand side of the KKT-System", GSL_ENOMEM, 0); } return GSL_SUCCESS; } static int mg_pdip_set (void *vstate, const gsl_cqp_data *cqp, gsl_vector *x, gsl_vector *y, gsl_vector *z, double *gap, double *residuals_norm, double *data_norm, double *infeasibility, double *infeasibility_min) { int status; size_t i, j, debug=0; mg_pdip_state *state = (mg_pdip_state *) vstate; /* Initial points */ if( cqp->A->size1 != 0 ) status = pdip_initial_point_feasible_x(cqp->A, cqp->b, x); else gsl_vector_set_zero(x); status = pdip_initial_point_feasible_s(cqp->C, cqp->d, x, state->s); if( cqp->A->size1 != 0 ) status = pdip_initial_point_y(cqp->Q, cqp->q, cqp->A, x, y); status = pdip_initial_point_z(z); status = pdip_initial_point_strict_feasible(z, state->s); /* Dualtity gap */ status = gsl_blas_ddot(z, state->s, gap); *gap /= (double) cqp->C->size1; status = build_kkt_matrix(cqp, z, state->s, state->kkt_matrix); state->tau = 3.0; /* for the convergence conditions */ /* data_norm = ||Q,A,C,q,b,d||_{infinity} */ *data_norm = gsl_matrix_max_norm(cqp->Q); if( cqp->A->size1 != 0 ) *data_norm = GSL_MAX_DBL(*data_norm, gsl_matrix_max_norm(cqp->A)); *data_norm = GSL_MAX_DBL(*data_norm, gsl_matrix_max_norm(cqp->C)); *data_norm = GSL_MAX_DBL(*data_norm, gsl_vector_max_norm(cqp->q)); if( cqp->A->size1 != 0 ) *data_norm = GSL_MAX_DBL(*data_norm, gsl_vector_max_norm(cqp->b)); *data_norm = GSL_MAX_DBL(*data_norm, gsl_vector_max_norm(cqp->d)); state->data_norm = *data_norm; /* ||r||_{infinity}=||r_Q, r_A, r_C||_{infinity} */ status = compute_residuals(cqp, x, y, z, state->s, state->r); *residuals_norm = gsl_vector_max_norm(state->r); *infeasibility = (*residuals_norm+compute_gap_infeasible_points(cqp, x, y, z))/(state->data_norm); *infeasibility_min = *infeasibility; /* Gondzio's paameters */ state->k_max = 0; state->beta_min = 0.1; state->beta_max = 10.0; state->delta_alpha = 0.1; state->gamma = 0.1; if(debug) { printf("\nStart points:\n"); print_vectors(x, y, z, state->s); printf("\nDuality gap: %e\n",*gap); printf("\nKKT-matrix:\n"); for(i=0; ikkt_matrix->size1; i++) { for(j=0; jkkt_matrix->size2; j++) printf("%4.2f ",gsl_matrix_get(state->kkt_matrix,i,j)); printf("\n"); } printf("\n"); } return GSL_SUCCESS; } static int mg_pdip_iterate (void *vstate, const gsl_cqp_data *cqp, gsl_vector *x, gsl_vector *y, gsl_vector *z, double *gap, double *residuals_norm, double *infeasibility, double *infeasibility_min) { size_t i, j, debug=0; int status, signum; double sigma, alpha, alpha_gondzio; double tmp_v, tmp_vt, tmp_d; gsl_vector_view r_block, delta_block; mg_pdip_state *state = (mg_pdip_state *) vstate; gsl_permutation * p; gsl_vector * delta; gsl_vector * delta_s; gsl_vector * r_zs; gsl_vector * delta_gondzio; gsl_vector * delta_s_gondzio; p = gsl_permutation_alloc(state->kkt_matrix->size1); if(p == 0) { GSL_ERROR_VAL ("failed to initialize space for permutation vector", GSL_ENOMEM, 0); } delta = gsl_vector_alloc(state->kkt_matrix->size2); if(delta == 0) { gsl_permutation_free(p); GSL_ERROR_VAL ("failed to initialize space for predictor step", GSL_ENOMEM, 0); } delta_gondzio = gsl_vector_alloc(state->kkt_matrix->size2); if(delta_gondzio == 0) { gsl_vector_free(delta); gsl_permutation_free(p); GSL_ERROR_VAL ("failed to initialize space for predictor step", GSL_ENOMEM, 0); } delta_s_gondzio = gsl_vector_alloc(z->size); if(delta_s_gondzio == 0) { gsl_vector_free(delta_gondzio); gsl_vector_free(delta); gsl_permutation_free(p); GSL_ERROR_VAL ("failed to initialize space for LM s", GSL_ENOMEM, 0); } delta_s = gsl_vector_alloc(z->size); if(delta_s == 0) { gsl_vector_free(delta_s_gondzio); gsl_vector_free(delta_gondzio); gsl_vector_free(delta); gsl_permutation_free(p); GSL_ERROR_VAL ("failed to initialize space for LM s", GSL_ENOMEM, 0); } r_zs = gsl_vector_alloc(z->size); if(r_zs == 0) { gsl_vector_free(delta_s); gsl_vector_free(delta_s_gondzio); gsl_vector_free(delta_gondzio); gsl_vector_free(delta); gsl_permutation_free(p); GSL_ERROR_VAL ("failed to initialize space for LM s", GSL_ENOMEM, 0); } /* the right-hand side of the KKT-system: r = -(r_Q, r_A, r_C+Z^{-1}*r_zs) */ /* the vecors of variables: delta = (delta_x, -delta_y, -delta_z) */ /* delta_s for the slack variable s: delta_s = Z^{-1}(-r_zs - S*delta_z) */ /********* Predictor Step ******************/ /* in the predictor step: r_zs = ZSe */ r_block = gsl_vector_subvector(state->r, cqp->Q->size1+cqp->A->size1, cqp->C->size1); status = gsl_blas_daxpy(1.0, state->s, &r_block.vector); gsl_blas_dscal(-1.0, state->r); /* solve the KKT-system: */ /* evaluate the LU-decomposition of the KKT-matrix*/ status = gsl_linalg_LU_decomp(state->kkt_matrix, p, &signum); /* find the predictor step */ status = gsl_linalg_LU_solve(state->kkt_matrix, p, state->r, delta); for(i=0; isize; i++) { /* find delta_s for the slack variable s: delta_s = Z^{-1}(-r_zs - S*delta_z) */ gsl_vector_set(delta_s, i, gsl_vector_get(state->s,i)* (gsl_vector_get(delta,cqp->Q->size2+cqp->A->size1+i)/gsl_vector_get(z,i)-1.0)); } /* find the stepsize of the predictor step */ delta_block = gsl_vector_subvector(delta, cqp->Q->size1+cqp->A->size1, cqp->C->size1); alpha = step_length(state->s, z, delta_s, &delta_block.vector); if(debug) { printf("\n *** Predictor Step ***\n"); printf("\nthe right-hand side of the KKT-system:\n"); for(i=0; ir->size; i++) printf("r[%d]=%f ",(int)i,gsl_vector_get(state->r,i)); printf("\n"); printf("\nsolution (delta_x,-delta_y,-delta_z):\n"); for(i=0; isize; i++) printf("%6.3f ",gsl_vector_get(delta,i)); printf("\n"); printf("\ndelta_s\n"); for(i=0; isize; i++) printf("%6.3f ",gsl_vector_get(delta_s,i)); printf("\n"); printf("the stepsize for the predictor step=%f\n",alpha); } /************ Evaluation of the centering parameter sigma ***************/ /* sigma = (gap_aff/gap)^tau */ sigma = 0.0; for(i=0; isize; i++) { sigma += (gsl_vector_get(z,i) - alpha*gsl_vector_get(delta, cqp->Q->size2+cqp->A->size1+i))* (gsl_vector_get(state->s,i) + alpha*gsl_vector_get(delta_s,i)); } sigma /= (cqp->C->size1*(*gap)); sigma = pow(sigma, state->tau); if(debug) printf("the centering parameter sigma =%f\n",sigma); /************ Corrector Step ******************/ /* modify the right-hand side of the kkt-system in oder to find the Mehrotra's corrector step */ r_block = gsl_vector_subvector(state->r, cqp->Q->size1+cqp->A->size1, cqp->C->size1); for(i=0; iC->size1; i++) { tmp_d = -sigma*(*gap)-gsl_vector_get(delta, cqp->Q->size2+cqp->A->size1+i)*gsl_vector_get(delta_s, i); gsl_vector_set(r_zs, i, gsl_vector_get(z,i)*gsl_vector_get(state->s,i)+tmp_d); gsl_vector_set(&r_block.vector, i, gsl_vector_get(&r_block.vector, i)-tmp_d/gsl_vector_get(z,i)); } /* find the corrector step */ status = gsl_linalg_LU_solve(state->kkt_matrix, p, state->r, delta); for(i=0; isize; i++) { /* delta_s = Z^{-1}*(-r_zs-S*delta_z) */ gsl_vector_set(delta_s, i, (-gsl_vector_get(r_zs,i)+gsl_vector_get(delta,cqp->Q->size2+cqp->A->size1+i) *gsl_vector_get(state->s, i))/gsl_vector_get(z,i)); } /* evaluate the stepsize */ delta_block = gsl_vector_subvector(delta, cqp->Q->size1+cqp->A->size1, cqp->C->size1); alpha = step_length(state->s, z, delta_s, &delta_block.vector); if(debug) { printf("\n *** Corrector Step ***\n"); printf("the right-hand side:\n"); for(i=0; ir->size; i++) printf("r[%d]=%f ",(int)i,gsl_vector_get(state->r,i)); printf("\n"); for(i=0; isize; i++) printf("%6.3f ",gsl_vector_get(delta,i)); printf("\n"); printf("\ndelta_s\n"); for(i=0; isize; i++) printf("%6.3f ",gsl_vector_get(delta_s,i)); printf("\n"); printf("the stepsize for the corrector step=%f\n",alpha); } /* Gondzio's centrality correction steps */ i=0; while(ik_max) { alpha_gondzio = GSL_MIN_DBL(alpha+state->delta_alpha, 1.0); r_block = gsl_vector_subvector(state->r, cqp->Q->size1+cqp->A->size1, cqp->C->size1); for(j=0; jsize; j++) { tmp_v = (gsl_vector_get(z,j)-alpha_gondzio*gsl_vector_get(delta,cqp->Q->size1+cqp->A->size1+j))* (gsl_vector_get(state->s,j)+alpha_gondzio*gsl_vector_get(delta_s,j)); tmp_vt = GSL_MIN_DBL(GSL_MAX_DBL(tmp_v,state->beta_min*sigma*(*gap)),state->beta_max*sigma*(*gap)); gsl_vector_set(r_zs, j, gsl_vector_get(r_zs,j)-(tmp_vt-tmp_v)); gsl_vector_set(&r_block.vector, j, gsl_vector_get(&r_block.vector,j)+(tmp_vt-tmp_v)/gsl_vector_get(z,j)); } status = gsl_linalg_LU_solve(state->kkt_matrix, p, state->r, delta_gondzio); for(j=0; jsize; j++) { gsl_vector_set(delta_s_gondzio, j, (-gsl_vector_get(r_zs,j)+gsl_vector_get(delta_gondzio,cqp->Q->size2+cqp->A->size1+j) *gsl_vector_get(state->s, j))/gsl_vector_get(z,j)); } /* evaluate the stepsize */ delta_block = gsl_vector_subvector(delta_gondzio, cqp->Q->size1+cqp->A->size1, cqp->C->size1); alpha_gondzio = step_length(state->s, z, delta_s, &delta_block.vector); if(alpha_gondzio >= alpha+state->gamma*state->delta_alpha) { i++; alpha = alpha_gondzio; status = gsl_blas_dcopy(delta_gondzio, delta); status = gsl_blas_dcopy(delta_s_gondzio, delta_s); } else break; } /* heuristic for step length */ alpha = GSL_MIN_DBL(0.995*alpha, 1.0); /* Update */ /* x^k = x^k + alpha*delta_x */ delta_block = gsl_vector_subvector(delta, 0, cqp->Q->size1); status = gsl_blas_daxpy(alpha, &delta_block.vector, x); /* y^k = y^k - alpha*(-delta_y) */ if( y != 0 ) { delta_block = gsl_vector_subvector(delta, cqp->Q->size1, cqp->A->size1); status = gsl_blas_daxpy(-alpha, &delta_block.vector, y); } /* z^k = z^k - alpha*(-delta_z) */ delta_block = gsl_vector_subvector(delta, cqp->Q->size1+cqp->A->size1, cqp->C->size1); status = gsl_blas_daxpy(-alpha, &delta_block.vector, z); /* s^k = s^k + alpha*(delta_s) */ status = gsl_blas_daxpy(alpha, delta_s, state->s); /* duality gap */ status = gsl_blas_ddot(z, state->s, gap); *gap /= cqp->C->size1; /* data for the next iteration */ status = compute_residuals(cqp, x, y, z, state->s, state->r); *residuals_norm = gsl_vector_max_norm(state->r); status = build_kkt_matrix(cqp, z, state->s, state->kkt_matrix); /* for the infeasibility test */ *infeasibility = (*residuals_norm+compute_gap_infeasible_points(cqp, x, y, z))/(state->data_norm); *infeasibility_min = GSL_MIN_DBL(*infeasibility, *infeasibility_min); if(debug) { printf("current iteration points\n"); print_vectors(x, y, z, state->s); printf("\nduality gap: %e\n",*gap); } gsl_permutation_free(p); gsl_vector_free(delta); gsl_vector_free(delta_gondzio); gsl_vector_free(delta_s_gondzio); gsl_vector_free(delta_s); gsl_vector_free(r_zs); return GSL_SUCCESS; } static void mg_pdip_free (void *vstate) { mg_pdip_state *state = (mg_pdip_state *) vstate; gsl_vector_free(state->s); gsl_matrix_free(state->kkt_matrix); gsl_vector_free(state->r); } /* static int mg_pdip_restart (void *vstate) { mg_pdip_state *state = (mg_pdip_state *) vstate; return GSL_SUCCESS; } */ static int compute_residuals(const gsl_cqp_data * cqp, const gsl_vector *x, const gsl_vector *y, const gsl_vector *z, const gsl_vector *s, gsl_vector *r) { int status; gsl_vector_view r_block; /*gsl_cqp_geconstraints * constraints = (gsl_cqp_geconstraints *) cqp->constraints;*/ /* r_Q=Qx+q-A^ty-C^tz */ r_block = gsl_vector_subvector(r, 0, cqp->Q->size1); status = gsl_blas_dcopy(cqp->q, &r_block.vector); status = gsl_blas_dsymv(CblasUpper, 1.0, cqp->Q, x, 1.0, &r_block.vector); /*status = gsl_blas_dgemv(CblasNoTrans, 1.0, cqp->Q, x, 1.0, r_Q);*/ if( cqp->A->size1 != 0 ) status = gsl_blas_dgemv(CblasTrans, -1.0, cqp->A, y, 1.0, &r_block.vector); status = gsl_blas_dgemv(CblasTrans, -1.0, cqp->C, z, 1.0, &r_block.vector); /* r_A=Ax-b */ if( cqp->A->size1 != 0 ) { r_block = gsl_vector_subvector(r, cqp->Q->size1, cqp->A->size1); status = gsl_blas_dcopy(cqp->b, &r_block.vector); status = gsl_blas_dgemv(CblasNoTrans, 1.0, cqp->A, x, -1.0, &r_block.vector); } /* r_C=Cx-s-d */ r_block = gsl_vector_subvector(r, cqp->Q->size1+cqp->A->size1, cqp->C->size1); status = gsl_blas_dcopy(s, &r_block.vector); status = gsl_blas_daxpy(1.0, cqp->d, &r_block.vector); status = gsl_blas_dgemv(CblasNoTrans, 1.0, cqp->C, x, -1.0, &r_block.vector); return GSL_SUCCESS; } static int build_kkt_matrix(const gsl_cqp_data * cqp, const gsl_vector *z, const gsl_vector *s, gsl_matrix * kkt_matrix) { size_t i; int status; gsl_matrix_view kkt_block; /*KKT - Matrix |Q A^t C^t | kkt_matrix =|A 0 0 | |C 0 -Z^{-1}S| */ /* 1. Block */ kkt_block = gsl_matrix_submatrix(kkt_matrix, 0, 0, cqp->Q->size1, cqp->Q->size2); status = gsl_matrix_memcpy(&kkt_block.matrix, cqp->Q); /* 2. Block */ if( cqp->A->size1 != 0 ) { kkt_block = gsl_matrix_submatrix(kkt_matrix, 0, cqp->Q->size2, cqp->A->size2, cqp->A->size1); status = gsl_matrix_transpose_memcpy(&kkt_block.matrix, cqp->A); } /* 3. Block */ kkt_block = gsl_matrix_submatrix(kkt_matrix, 0, cqp->Q->size2+cqp->A->size1, cqp->C->size2, cqp->C->size1); status = gsl_matrix_transpose_memcpy(&kkt_block.matrix, cqp->C); /* 4. Block */ if( cqp->A->size1 != 0 ) { kkt_block = gsl_matrix_submatrix(kkt_matrix, cqp->Q->size1, 0, cqp->A->size1, cqp->A->size2); status = gsl_matrix_memcpy(&kkt_block.matrix, cqp->A); } /* 5. Block */ kkt_block = gsl_matrix_submatrix(kkt_matrix, cqp->Q->size1+cqp->A->size1, 0, cqp->C->size1, cqp->C->size2); status = gsl_matrix_memcpy(&kkt_block.matrix, cqp->C); /* Null Block */ kkt_block = gsl_matrix_submatrix(kkt_matrix, cqp->Q->size1, cqp->Q->size2, cqp->A->size1+cqp->C->size1, cqp->A->size1+cqp->C->size1); gsl_matrix_set_zero(&kkt_block.matrix); /* 6. Block */ for(i=cqp->Q->size1+cqp->A->size1; isize1; i++) { gsl_matrix_set(kkt_matrix, i,i, -gsl_vector_get(s, i-(cqp->Q->size1+cqp->A->size1))/ gsl_vector_get(z, i-(cqp->Q->size1+cqp->A->size1))); } return GSL_SUCCESS; } double step_length(const gsl_vector *s, const gsl_vector *z, const gsl_vector *delta_s, const gsl_vector *delta_z) { double alpha = 1.0; size_t i; for(i=0; isize; i++) { if(gsl_vector_get(delta_z,i) > 0.0) alpha = GSL_MIN_DBL(alpha, gsl_vector_get(z,i)/gsl_vector_get(delta_z,i)); if(gsl_vector_get(delta_s,i) < 0.0) alpha = GSL_MIN_DBL(alpha, -gsl_vector_get(s,i)/gsl_vector_get(delta_s,i)); } return alpha; } static double compute_gap_infeasible_points(const gsl_cqp_data *cqp, const gsl_vector *x, const gsl_vector *y, const gsl_vector *z) { double g, tmp_d; int status; gsl_vector * tmp_v = gsl_vector_alloc(cqp->q->size); status = gsl_blas_dcopy(cqp->q, tmp_v); status = gsl_blas_dsymv(CblasUpper, 1.0, cqp->Q, x, 1.0, tmp_v); status = gsl_blas_ddot(x, tmp_v, &g); if( cqp->A->size1 != 0 ) { status = gsl_blas_ddot(cqp->b, y, &tmp_d); g -= tmp_d; } status = gsl_blas_ddot(cqp->d, z, &tmp_d); g -= tmp_d; gsl_vector_free(tmp_v); return g; } double gsl_matrix_max_norm(const gsl_matrix *M) { size_t i,j; double max_norm = 0.0; for(i=0; isize1; i++) for(j=0; jsize2; j++) max_norm = GSL_MAX_DBL(max_norm, fabs(gsl_matrix_get(M, i, j))); return max_norm; } double gsl_vector_max_norm(const gsl_vector *v) { size_t i; double max_norm = fabs(gsl_vector_get(v,0)); for(i=1; isize; i++) max_norm = GSL_MAX_DBL(max_norm, fabs(gsl_vector_get(v,i))); return max_norm; } static void print_vectors(const gsl_vector * x, const gsl_vector * y, const gsl_vector * z, const gsl_vector * s) { size_t i; printf("\nx[1 x %d]:\n",(int)x->size); for(i=0; isize; i++) printf("%f ",gsl_vector_get(x,i)); printf("\n"); if( y != NULL ) { printf("\ny[1 x %d]: (LM zu Ax=b)\n",(int)y->size); for(i=0; isize; i++) printf("%f ",gsl_vector_get(y,i)); printf("\n"); } printf("\nz[1 x %d]: (LM zu Cx>=d)\n",(int)z->size); for(i=0; isize; i++) printf("%f ",gsl_vector_get(z,i)); printf("\n"); printf("\ns[1 x %d]: (Slack zu Cx>=d)\n",(int)s->size); for(i=0; isize; i++) printf("%f ",gsl_vector_get(s,i)); printf("\n"); } static const gsl_cqpminimizer_type mg_pdip_type = { "mg_pdip", /* name of the method: Mehrotra-Gondzio primal-dual interior point method*/ sizeof (mg_pdip_state), &mg_pdip_alloc, &mg_pdip_set, &mg_pdip_iterate, /* &mg_pdip_restart, */ &mg_pdip_free }; const gsl_cqpminimizer_type * gsl_cqpminimizer_mg_pdip = &mg_pdip_type; pfstools-2.2.0/src/tmo/mantiuk08/cqp/initial_point.h0000664000701400070140000000320614105165616021070 0ustar rkm38rkm38/** * @brief Convex Quadratic Programming library * * From: * http://ra.uni-trier.de/~huebner/software.html * * ---------------------------------------------------------------------- * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Ewgenij Hübner * * $$ */ #ifdef __cplusplus # define __BEGIN_DECLS extern "C" { # define __END_DECLS } #else # define __BEGIN_DECLS /* empty */ # define __END_DECLS /* empty */ #endif __BEGIN_DECLS int pdip_initial_point_feasible_x(const gsl_matrix * A, const gsl_vector *b, gsl_vector *x); int pdip_initial_point_feasible_s(const gsl_matrix * C, const gsl_vector *d, const gsl_vector *x, gsl_vector *s); int pdip_initial_point_y(const gsl_matrix *Q, const gsl_vector *q, const gsl_matrix *A, const gsl_vector *x, gsl_vector *y); int pdip_initial_point_z(gsl_vector *z); int pdip_initial_point_strict_feasible(gsl_vector *x, gsl_vector *s); __END_DECLS pfstools-2.2.0/src/tmo/mantiuk08/cqp/initial_point.cpp0000664000701400070140000001233314105165616021424 0ustar rkm38rkm38/** * @brief Convex Quadratic Programming library * * From: * http://ra.uni-trier.de/~huebner/software.html * * ---------------------------------------------------------------------- * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Ewgenij Hübner * * $$ */ #include #include #include #include "initial_point.h" int pdip_initial_point_feasible_x(const gsl_matrix *A, const gsl_vector *b, gsl_vector *x) { int status, signum; /* x = A^t*(AA^t)^{-1}*b */ gsl_matrix * AA_t; gsl_vector * tmp; gsl_permutation * p; AA_t = gsl_matrix_calloc(A->size1, A->size1); if(AA_t == 0) { GSL_ERROR_VAL ("failed to initialize space for finding initial point", GSL_ENOMEM, 0); } tmp = gsl_vector_alloc(A->size1); if(tmp == 0) { gsl_matrix_free(AA_t); GSL_ERROR_VAL ("failed to initialize space for finding initial point", GSL_ENOMEM, 0); } p = gsl_permutation_alloc(A->size1); if(p == 0) { gsl_vector_free(tmp); gsl_matrix_free(AA_t); GSL_ERROR_VAL ("failed to initialize space for finding initial point", GSL_ENOMEM, 0); } /* status = gsl_blas_dsyrk(CblasUpper, CblasNoTrans, 1.0, cqp->A, 0.0, AA_t); */ status = gsl_blas_dgemm(CblasNoTrans, CblasTrans, 1.0, A, A, 0.0, AA_t); /* (AA^t)tmp=b */ status = gsl_linalg_LU_decomp(AA_t, p, &signum); status = gsl_linalg_LU_solve(AA_t, p, b, tmp); /* status = gsl_blas_dtrsv(CblasUpper, CblasNoTrans, CblasNonUnit, AA_t, tmp); tmp=(AA^t)^{-1}tmp */ status = gsl_blas_dgemv(CblasTrans, 1.0, A, tmp, 0.0, x); gsl_matrix_free(AA_t); gsl_vector_free(tmp); gsl_permutation_free(p); return GSL_SUCCESS; } int pdip_initial_point_feasible_s(const gsl_matrix * C, const gsl_vector *d, const gsl_vector *x, gsl_vector *s) { int status; /* s=Cx-d */ status = gsl_blas_dcopy(d, s); status = gsl_blas_dgemv(CblasNoTrans, 1.0, C, x, -1.0, s); return GSL_SUCCESS; } int pdip_initial_point_y(const gsl_matrix *Q, const gsl_vector *q, const gsl_matrix *A, const gsl_vector *x, gsl_vector *y) { int status, signum; /* y=(AA^t)^{-1}(A(Qx+q)) */ gsl_matrix * AA_t; gsl_vector * tmp; gsl_permutation * p; AA_t = gsl_matrix_calloc(A->size1, A->size1); if(AA_t == 0) { GSL_ERROR_VAL ("failed to initialize space for finding initial point", GSL_ENOMEM, 0); } tmp = gsl_vector_alloc(q->size); if(tmp == 0) { gsl_matrix_free(AA_t); GSL_ERROR_VAL ("failed to initialize space for finding initial point", GSL_ENOMEM, 0); } p = gsl_permutation_alloc(AA_t->size1); if(p == 0) { gsl_vector_free(tmp); gsl_matrix_free(AA_t); GSL_ERROR_VAL ("failed to initialize space for finding initial point", GSL_ENOMEM, 0); } status = gsl_blas_dcopy(q, tmp); status = gsl_blas_dsymv(CblasUpper, 1.0, Q, x, 1.0, tmp); status = gsl_blas_dgemv(CblasNoTrans, 1.0, A, tmp, 0.0, y); /* status = gsl_blas_dsyrk(CblasUpper, CblasNoTrans, 1.0, cqp->A, 0.0, AA_t); */ gsl_vector_free(tmp); tmp = gsl_vector_alloc(y->size); if(tmp == 0) { gsl_permutation_free(p); gsl_matrix_free(AA_t); GSL_ERROR_VAL ("failed to initialize space for finding initial point", GSL_ENOMEM, 0); } status = gsl_blas_dcopy(y, tmp); status = gsl_blas_dgemm(CblasNoTrans, CblasTrans, 1.0, A, A, 0.0, AA_t); status = gsl_linalg_LU_decomp(AA_t, p, &signum); status = gsl_linalg_LU_solve(AA_t, p, tmp, y); /* status = gsl_blas_dtrsv(CblasUpper, CblasNoTrans, CblasNonUnit, AA_t, y); */ gsl_matrix_free(AA_t); gsl_vector_free(tmp); gsl_permutation_free(p); return GSL_SUCCESS; } int pdip_initial_point_z(gsl_vector *z) { double delta_z = 0.1; gsl_vector_set_all(z, delta_z); return GSL_SUCCESS; } int pdip_initial_point_strict_feasible(gsl_vector *x, gsl_vector *s) { double delta_x, delta_s, xs=0.0, sum_x=0.0, sum_s=0.0, tmp, rg=1e-10; size_t i; int status; delta_x = GSL_MAX_DBL(-1.5*gsl_vector_min(x),0.0); delta_s = GSL_MAX_DBL(-1.5*gsl_vector_min(s),0.0); if(delta_x < rg && delta_s < rg) { status = gsl_blas_ddot(x, s, &tmp); if(tmp < rg) /* the initial point is optimal */ return GSL_SUCCESS; } for(i=0; isize; i++) { xs += (gsl_vector_get(x,i)+delta_x)*(gsl_vector_get(s,i)+delta_s); sum_x += gsl_vector_get(x,i); sum_s += gsl_vector_get(s,i); } sum_x += delta_x*(x->size); sum_s += delta_s*(s->size); delta_x += 0.5*xs/sum_s; delta_s += 0.5*xs/sum_x; status = gsl_vector_add_constant(x, delta_x); status = gsl_vector_add_constant(s, delta_s); return GSL_SUCCESS; } pfstools-2.2.0/src/tmo/mantiuk08/cqp/cqpminimizer.cpp0000664000701400070140000001551414105165616021275 0ustar rkm38rkm38/** * @brief Convex Quadratic Programming library * * From: * http://ra.uni-trier.de/~huebner/software.html * * ---------------------------------------------------------------------- * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Ewgenij Hübner * * $$ */ #include #include #include #include #include #include "gsl_cqp.h" gsl_cqpminimizer * gsl_cqpminimizer_alloc (const gsl_cqpminimizer_type * T, const size_t n, const size_t me, const size_t mi) { int status; gsl_cqpminimizer *minimizer = (gsl_cqpminimizer *) malloc (sizeof (gsl_cqpminimizer)); if (minimizer == 0) { GSL_ERROR_VAL ("failed to allocate space for cqpminimizer", GSL_ENOMEM, 0); } minimizer->type = T; if(n <= 0) { free (minimizer); GSL_ERROR_VAL ("the problem dimension n must be greater than 0", GSL_ENOMEM, 0); } /* if(me < 0) { free (minimizer); GSL_ERROR_VAL ("the number of equality constrains me must be greater or equal to 0", GSL_ENOMEM, 0); }*/ if(mi <= 0) { free (minimizer); GSL_ERROR_VAL ("the number of inequality constrains me must be greater or equal to 1", GSL_ENOMEM, 0); } minimizer->x = gsl_vector_alloc(n); if (minimizer->x == 0) { free (minimizer); GSL_ERROR_VAL ("failed to allocate space for x", GSL_ENOMEM, 0); } if( me != 0 ) { minimizer->y = gsl_vector_alloc(me); if(minimizer->y ==0) { gsl_vector_free(minimizer->x); free(minimizer); GSL_ERROR_VAL ("failed to allocate space for the Lagrange multiplier y", GSL_ENOMEM, 0); } } else minimizer->y = 0; minimizer->z = gsl_vector_alloc(mi); if(minimizer->z ==0) { gsl_vector_free(minimizer->y); gsl_vector_free(minimizer->x); free(minimizer); GSL_ERROR_VAL ("failed to allocate space for the Lagrange multiplier z", GSL_ENOMEM, 0); } minimizer->state = malloc (T->size); if (minimizer->state == 0) { gsl_vector_free (minimizer->x); free (minimizer); GSL_ERROR_VAL ("failed to allocate space for minimizer state", GSL_ENOMEM, 0); } status = (T->alloc) (minimizer->state, n, me, mi); if (status != GSL_SUCCESS) { free (minimizer->state); gsl_vector_free (minimizer->x); free (minimizer); GSL_ERROR_VAL ("failed to initialize minimizer state", GSL_ENOMEM, 0); } return minimizer; } int gsl_cqpminimizer_set (gsl_cqpminimizer * minimizer, gsl_cqp_data * cqp) { if (cqp->Q->size1 != cqp->Q->size2) { GSL_ERROR ("matrix Q is not square", GSL_EBADLEN); } if (cqp->q->size != cqp->Q->size1) { GSL_ERROR ("dim(q)!= #rows(Q)", GSL_EBADLEN); } if (cqp->Q->size1 != minimizer->x->size) { GSL_ERROR ("#columns(Q)!=dim(x)", GSL_EBADLEN); } if(cqp->A != 0 && cqp->b !=0 && cqp->A->size1 !=0) { if(cqp->A->size2 != minimizer->x->size) { GSL_ERROR ("#columns(A) != dim(x)", GSL_EBADLEN); } if(cqp->A->size1 != cqp->b->size) { GSL_ERROR ("#rows(A) != dim(b)", GSL_EBADLEN); } if(cqp->A->size1 != minimizer->y->size) { GSL_ERROR ("#rows(A) != me", GSL_EBADLEN); } } if(cqp->C->size2 != minimizer->x->size) { GSL_ERROR ("#columns(C) != dim(x)", GSL_EBADLEN); } if(cqp->C->size1 != cqp->d->size) { GSL_ERROR ("#rows(C) != dim(d)", GSL_EBADLEN); } if(cqp->C->size1 != minimizer->z->size) { GSL_ERROR ("#rows(C) != mi", GSL_EBADLEN); } minimizer->cqp = cqp; return (minimizer->type->set) (minimizer->state, minimizer->cqp, minimizer->x, minimizer->y, minimizer->z, &(minimizer->gap), &(minimizer->residuals_norm), &(minimizer->data_norm), &(minimizer->quantity_of_infeasibility), &(minimizer->quantity_of_infeasibility_min)); } void gsl_cqpminimizer_free (gsl_cqpminimizer * minimizer) { (minimizer->type->free) (minimizer->state); gsl_vector_free (minimizer->x); if( minimizer->y != 0 ) gsl_vector_free (minimizer->y); gsl_vector_free (minimizer->z); free(minimizer->state); free(minimizer); } int gsl_cqpminimizer_iterate (gsl_cqpminimizer * minimizer) { return (minimizer->type->iterate) (minimizer->state, minimizer->cqp, minimizer->x, minimizer->y, minimizer->z, &(minimizer->gap), &(minimizer->residuals_norm), &(minimizer->quantity_of_infeasibility), &(minimizer->quantity_of_infeasibility_min)); } /* int gsl_cqpminimizer_restart (gsl_cqpminimizer * minimizer) { return (minimizer->type->restart) (minimizer->state); } */ const char * gsl_cqpminimizer_name (const gsl_cqpminimizer * minimizer) { return minimizer->type->name; } gsl_vector * gsl_cqpminimizer_x (gsl_cqpminimizer * minimizer) { return minimizer->x; } gsl_vector * gsl_cqpminimizer_lm_eq (gsl_cqpminimizer * minimizer) { return minimizer->y; } gsl_vector * gsl_cqpminimizer_lm_ineq (gsl_cqpminimizer * minimizer) { return minimizer->z; } double gsl_cqpminimizer_f (gsl_cqpminimizer * minimizer) { /* the value of the objective at the point x */ /* f = 0.5*(x^t)Qx+(q^t)x */ double f; int status; gsl_vector * tmp = gsl_vector_alloc(minimizer->x->size); if(tmp == 0) { GSL_ERROR_VAL ("failed to initialize workspace", GSL_ENOMEM, 0); } status = gsl_blas_dcopy(minimizer->cqp->q, tmp); status = gsl_blas_dsymv(CblasUpper, 0.5, minimizer->cqp->Q, minimizer->x, 1.0, tmp); status = gsl_blas_ddot(minimizer->x, tmp, &f); gsl_vector_free(tmp); return f; } double gsl_cqpminimizer_gap (gsl_cqpminimizer * minimizer) { return minimizer->gap; } double gsl_cqpminimizer_residuals_norm (gsl_cqpminimizer *minimizer) { return minimizer->residuals_norm; } int gsl_cqpminimizer_test_convergence(gsl_cqpminimizer * minimizer, double eps_gap, double eps_residuals) { if(minimizer->gap <= eps_gap && minimizer->residuals_norm <= eps_residuals*minimizer->data_norm) return GSL_SUCCESS; else return GSL_CONTINUE; } int gsl_cqp_minimizer_test_infeasibility(gsl_cqpminimizer * minimizer, double eps_infeasible) { if(minimizer->quantity_of_infeasibility > eps_infeasible && minimizer->quantity_of_infeasibility > pow(10.0, 4) * minimizer->quantity_of_infeasibility_min) return GSL_SUCCESS; else return GSL_CONTINUE; } pfstools-2.2.0/src/tmo/mantiuk08/cqp/README.txt0000664000701400070140000000033414105165616017552 0ustar rkm38rkm38This directory contains: CQP - Convex Quadratic Programming by Ewgenij Hübner URL: http://ra.uni-trier.de/~huebner/software.html with several modification that allow to run the solver without equality constraints. pfstools-2.2.0/src/tmo/mantiuk08/display_function.h0000664000701400070140000000503414105165616021016 0ustar rkm38rkm38/** * @brief Display Adaptive TMO * * From: * Rafal Mantiuk, Scott Daly, Louis Kerofsky. * Display Adaptive Tone Mapping. * To appear in: ACM Transactions on Graphics (Proc. of SIGGRAPH'08) 27 (3) * http://www.mpi-inf.mpg.de/resources/hdr/datmo/ * * This file is a part of PFSTMO package. * ---------------------------------------------------------------------- * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Rafal Mantiuk, * * $Id: display_function.h,v 1.3 2008/06/16 18:42:58 rafm Exp $ */ #ifndef DISPLAY_FUNCTION_H #define DISPLAY_FUNCTION_H #include class DisplayFunction { public: /** Convert input luminance (cd/m^2) to pixel value (0-1) */ virtual float inv_display( float L ) = 0; /** Convert pixel value (0-1) to input luminance (cd/m^2) */ virtual float display( float pix ) = 0; virtual void print( FILE *fh ) = 0; virtual ~DisplayFunction() { } }; /** * Gamma Gain Black and Ambient display model */ class DisplayFunctionGGBA : public DisplayFunction { float gamma, L_max, L_offset, L_black, E_amb, screen_refl; public: DisplayFunctionGGBA( float gamma, float L_max, float L_black, float E_amb, float screen_refl ); DisplayFunctionGGBA( const char *predefined ); float inv_display( float L ); float display( float pix ); void print( FILE *fh ); private: void init( float gamma, float L_max, float L_black, float E_amb, float screen_refl ); }; class DisplayFunctionLUT : public DisplayFunction { float *pix_lut, *L_lut; size_t lut_size; public: DisplayFunctionLUT( const char *file_name ); ~DisplayFunctionLUT(); float inv_display( float L ); float display( float pix ); void print( FILE *fh ); }; DisplayFunction *createDisplayFunctionFromArgs( int &argc, char* argv[] ); #endif pfstools-2.2.0/src/tmo/mantiuk08/display_adaptive_tmo.cpp0000664000701400070140000010773714105165616022215 0ustar rkm38rkm38/** * @brief Display Adaptive TMO * * From: * Rafal Mantiuk, Scott Daly, Louis Kerofsky. * Display Adaptive Tone Mapping. * To appear in: ACM Transactions on Graphics (Proc. of SIGGRAPH'08) 27 (3) * http://www.mpi-inf.mpg.de/resources/hdr/datmo/ * * This file is a part of PFSTMO package. * ---------------------------------------------------------------------- * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Rafal Mantiuk, * * $Id: display_adaptive_tmo.cpp,v 1.26 2013/12/28 14:00:54 rafm Exp $ */ #include #include #include #include #include #include #include #include "display_adaptive_tmo.h" #include #include #include #include #include "cqp/gsl_cqp.h" #include // Computing Conditional Density takes about 90% of the time #define PROGRESS_CDF 90 #define MIN_PHVAL 1e-8f // Minimum value allowed in HDR images #define MAX_PHVAL 1e8f // Maximum value allowed in HDR images /** * Simple RAII wrapper for gsl_matrix */ class auto_matrix { gsl_matrix *m; public: auto_matrix(gsl_matrix *m): m(m) {} ~auto_matrix() { gsl_matrix_free(m); } operator gsl_matrix* () { return m; } }; /** * Simple RAII wrapper for gsl_vector */ class auto_vector { gsl_vector *v; public: auto_vector(gsl_vector *v): v(v) {} ~auto_vector() { gsl_vector_free(v); } operator gsl_vector* () { return v; } }; /** * Simple RAII wrapper for gsl_cqpminimizer */ class auto_cqpminimizer { gsl_cqpminimizer *v; public: auto_cqpminimizer(gsl_cqpminimizer *v): v(v) {} ~auto_cqpminimizer() { gsl_cqpminimizer_free(v); } operator gsl_cqpminimizer* () { return v; } }; // =============== Utils ============== #define round_int( x ) (int)((x) + 0.5) #define sign( x ) ( (x)<0 ? -1 : 1 ) inline float safe_log10( float x, const float min_x = MIN_PHVAL, const float max_x = MAX_PHVAL ) { if( x < min_x ) return log10(min_x); else if( x > max_x ) return log10(max_x); else return log10( x ); } /** * Find the lowest non-zero value. Used to avoid log10(0). */ float min_positive( const float *x, size_t len ) { float min_val = MAX_PHVAL; for( int k=0; k < len; k++ ) if( unlikely(x[k] < min_val && x[k] > 0) ) min_val = x[k]; return min_val; } void mult_rows( const gsl_matrix *A, const gsl_vector *b, gsl_matrix *C ) { assert( A->size1 == b->size ); for( int j=0; j < A->size2; j++ ) for( int i=0; i < A->size1; i++ ) gsl_matrix_set( C, i, j, gsl_matrix_get(A,i,j)*gsl_vector_get(b,i) ); } /** * Lookup table on a uniform array & interpolation * * x_i must be at least two elements * y_i must be initialized after creating an object */ class UniformArrayLUT { const double *x_i; size_t lut_size; double delta; bool own_y_i; public: double *y_i; UniformArrayLUT( size_t lut_size, const double *x_i, double *y_i = NULL ) : x_i( x_i ), lut_size( lut_size ), delta( x_i[1]-x_i[0] ) { if( y_i == NULL ) { this->y_i = new double[lut_size]; own_y_i = true; } else { this->y_i = y_i; own_y_i = false; } } UniformArrayLUT() : x_i( 0 ), y_i(0), lut_size( 0 ), delta( 0. ) {} UniformArrayLUT(const UniformArrayLUT& other) : x_i( other.x_i ), lut_size( other.lut_size ), delta( other.delta ) { this->y_i = new double[lut_size]; own_y_i = true; memcpy(this->y_i, other.y_i, lut_size * sizeof(double)); } UniformArrayLUT& operator = (const UniformArrayLUT& other) { this->lut_size = other.lut_size; this->delta = other.delta; this->x_i = other.x_i; this->y_i = new double[lut_size]; own_y_i = true; memcpy(this->y_i, other.y_i, lut_size * sizeof(double)); return *this; } ~UniformArrayLUT() { if( own_y_i ) delete []y_i; } double interp( double x ) { const double ind_f = (x - x_i[0])/delta; const size_t ind_low = (size_t)(ind_f); const size_t ind_hi = (size_t)ceil(ind_f); if( unlikely(ind_f < 0) ) // Out of range checks return y_i[0]; if( unlikely(ind_hi >= lut_size) ) return y_i[lut_size-1]; if( unlikely(ind_low == ind_hi) ) return y_i[ind_low]; // No interpolation necessary return y_i[ind_low] + (y_i[ind_hi]-y_i[ind_low])*(ind_f-(double)ind_low); // Interpolation } }; #define PFSEOL "\x0a" static void dumpPFS( const char *fileName, const int width, const int height, float *data, const char *channelName ) { FILE *fh = fopen( fileName, "wb" ); assert( fh != NULL ); fprintf( fh, "PFS1" PFSEOL "%d %d" PFSEOL "1" PFSEOL "0" PFSEOL "%s" PFSEOL "0" PFSEOL "ENDH", width, height, channelName ); for( int y = 0; y < height; y++ ) for( int x = 0; x < width; x++ ) { fwrite( &data[x+y*width], sizeof( float ), 1, fh ); } fclose( fh ); } void compute_gaussian_level( const int width, const int height, const pfstmo::Array2D& in, pfstmo::Array2D& out, int level, pfstmo::Array2D& temp ) { float kernel_a = 0.4; const int kernel_len = 5; const int kernel_len_2 = kernel_len/2; float kernel[kernel_len] = { 0.25f - kernel_a/2, 0.25f , kernel_a, 0.25f, 0.25f - kernel_a/2 }; int step = 1<= width ) ) l = 2*width - 2 - l; sum += in_raw[r*width+l] * kernel[j]; } temp_raw[r*width+c] = sum; } } // Filter columns for( int c=0; c < width; c++ ) { for( int r=0; r < height; r++ ) { float sum = 0; for( int j=0; j< kernel_len; j++ ) { int l = (j-kernel_len_2)*step+r; if( unlikely(l < 0) ) l = -l; if( unlikely(l >= height) ) l = 2*height - 2 - l; sum += temp_raw[l*width+c] * kernel[j]; } out_raw[r*width+c] = sum; } } } inline float clamp_channel( const float v ) { return (v > MIN_PHVAL ? v : MIN_PHVAL); } void print_matrix( gsl_matrix *m ) { for( int r=0; r < m->size1; r++ ) { fprintf( stderr, "[ " ); for( int c=0; c< m->size2; c++ ) { fprintf( stderr, "%g ", gsl_matrix_get( m, r, c ) ); } fprintf( stderr, "]\n" ); } } void print_vector( gsl_vector *v ) { for( int r=0; r < v->size; r++ ) { fprintf( stderr, "[ %g\t ]\n ", gsl_vector_get( v, r ) ); } } /** Compute conditional probability density function */ // round_int( (l_max-l_min)/delta ) + 1; #define X_COUNT (round_int((8.f+8.f)/0.1) + 1) class conditional_density: public datmoConditionalDensity { public: static const double l_min, l_max, delta; static double x_scale[X_COUNT]; // input log luminance scale double *g_scale; // contrast scale double *f_scale; // frequency scale const double g_max; double total; int x_count, g_count, f_count; // Number of elements double *C; // Conditional probability function conditional_density( const float pix_per_deg = 30.f ) : g_max( 0.7f ) { x_count = X_COUNT; g_count = round_int(g_max/delta)*2 + 1; // Find freq. band < 3 cyc per deg int f; for( f=0; f<8; f++ ) { float b_freq = 0.5f*pix_per_deg/(float)(1<= 0) && (x + g*x_count + f*x_count*g_count < x_count*g_count*f_count) ); return C[x + g*x_count + f*x_count*g_count]; } }; datmoConditionalDensity::~datmoConditionalDensity() { } const double conditional_density::l_min = -8.f, conditional_density::l_max = 8.f, conditional_density::delta = 0.1f; double conditional_density::x_scale[X_COUNT] = { 0 }; // input log luminance scale std::auto_ptr datmo_compute_conditional_density( int width, int height, const float *L, pfstmo_progress_callback progress_cb ) { if( progress_cb != NULL ) { progress_cb( 0 ); } pfstmo::Array2D buf_1(width, height); pfstmo::Array2D buf_2(width, height); pfstmo::Array2D temp(width, height); std::auto_ptr C(new conditional_density()); const float thr = 0.0043; // Approx. discrimination threshold in log10 const int pix_count = width*height; pfstmo::Array2D* LP_low = &buf_1; pfstmo::Array2D* LP_high = &buf_2; const float min_val = std::max( min_positive( L, pix_count ), MIN_PHVAL ); // Compute log10 of an image for( int i=0; i < pix_count; i++ ) (*LP_high)(i) = safe_log10( L[i], min_val ); bool warn_out_of_range = false; C->total = 0; for( int f=0; ff_count; f++ ) { compute_gaussian_level( width, height, *LP_high, *LP_low, f, temp ); // For debug purposes only // char fname[20]; // sprintf( fname, "l_%d.pfs", f+1 ); // dumpPFS( fname, width, height, LP_low, "Y" ); const int gi_tp = C->g_count/2+1; const int gi_tn = C->g_count/2-1; const int gi_t = C->g_count/2; for( int i=0; i < pix_count; i++ ) { float g = (*LP_high)(i) - (*LP_low)(i); // Compute band-pass int x_i = round_int( ((*LP_low)(i) - C->l_min)/C->delta ); if( unlikely(x_i < 0 || x_i >= C->x_count) ) { warn_out_of_range = true; continue; } int g_i = round_int( (g + C->g_max) / C->delta ); if( unlikely(g_i < 0 || g_i >= C->g_count) ) continue; if( g > thr && g < C->delta/2 ) { // above the threshold + (*C)(x_i,gi_tp,f)++; } else if( g < -thr && g > -C->delta/2 ) { // above the threshold - (*C)(x_i,gi_tn,f)++; } else (*C)(x_i,g_i,f)++; } for( int i = 0; i < C->x_count; i++ ) { // Special case: flat field and no gradients if( (*C)(i,gi_t,f) == 0 ) continue; bool gradient_exist = false; for( int m=0; mg_count; m++ ) if( m != gi_t && (*C)(i,m,f) != 0 ) { gradient_exist = true; break; } if( ~gradient_exist ) { // generate some gradient data to avoid bad conditioned problem (*C)(i,gi_tp,f)++; (*C)(i,gi_tn,f)++; } // Compute C->total for( int m=0; mg_count; m++ ) if( likely(m != gi_t) ) C->total += (*C)(i,m,f); } std::swap( LP_low, LP_high ); if( progress_cb != NULL ) { progress_cb( (f+1)*PROGRESS_CDF/C->f_count ); } } if( warn_out_of_range ) std::cerr << "Warning: Luminance value out of permissible range\n"; // For debugging purposes only // FILE *fh = fopen( "c_dens.dat", "wt" ); // std::cerr << "x: " << C->x_count << " g: " << C->g_count << " f: " << C->f_count << "\n"; // for( int i=0; i<(C->x_count*C->g_count*C->f_count); i++ ) { // fprintf( fh, "%g\n", C->C[i] ); // } // fclose( fh ); return (std::auto_ptr)C; } // =============== Quadratic programming solver ============== const static gsl_matrix null_matrix = {0}; const static gsl_vector null_vector = {0}; /* objective function: 0.5*(x^t)Qx+(q^t)x */ /* constraints: Cx>=d */ /* Ax=b; is not used in our problem */ int solve( gsl_matrix *Q, gsl_vector *q, gsl_matrix *C, gsl_vector *d, gsl_vector *x) { gsl_cqp_data cqpd; cqpd.Q = Q; cqpd.q = q; // Do not use any equality constraints (Ax=b) // Unfortunatelly GSL does not allow for 0-size vectors and matrices // As a work-around we create a phony gsl_matrix that has 0-size. // This matrix must not be passed to any gsl function! cqpd.A = &null_matrix; cqpd.b = &null_vector; cqpd.C = C; cqpd.d = d; size_t n = cqpd.Q->size1; size_t me = cqpd.b->size; size_t mi = cqpd.d->size; const size_t max_iter = 100; size_t iter=1; int status; const gsl_cqpminimizer_type * T; T = gsl_cqpminimizer_mg_pdip; auto_cqpminimizer s(gsl_cqpminimizer_alloc(T, n, me, mi)); status = gsl_cqpminimizer_set(s, &cqpd); bool verbose = false; if( verbose ) fprintf( stderr, "== Itn ======= f ======== ||gap|| ==== ||residual||\n\n"); do { status = gsl_cqpminimizer_iterate(s); status = gsl_cqpminimizer_test_convergence(s, 1e-10, 1e-10); if( verbose ) fprintf( stderr, "%4d %14.8f %13.6e %13.6e\n", (int)iter, gsl_cqpminimizer_f(s), gsl_cqpminimizer_gap(s), gsl_cqpminimizer_residuals_norm(s)); if(status == GSL_SUCCESS) { size_t j; if( verbose ) { fprintf( stderr, "\nMinimum is found at\n"); for(j=0; jsize; j++) fprintf( stderr, "%9.6f ",gsl_vector_get(gsl_cqpminimizer_x(s), j)); fprintf( stderr, "\n\n"); } // printf("\nLagrange-multipliers for Ax=b\n"); // for(j=0; jsize; j++) // printf("%9.6f ",gsl_vector_get(gsl_cqpminimizer_lm_eq(s), j)); // printf("\n\n"); // printf("\nLagrange-multipliers for Cx>=d\n"); // for(j=0; jsize; j++) // printf("%9.6f ",gsl_vector_get(gsl_cqpminimizer_lm_ineq(s), j)); // printf("\n\n"); } else { iter++; } } while(status == GSL_CONTINUE && iter<=max_iter); bool valid_solution = true; // If the solver bahaves instable, stop at this point if( status != GSL_SUCCESS ) { if( gsl_cqp_minimizer_test_infeasibility( s, 1e-10 ) != GSL_SUCCESS ) valid_solution = false; } if( valid_solution ) gsl_vector_memcpy( x, gsl_cqpminimizer_x(s) ); return GSL_SUCCESS; } // =============== HVS functions ============== double contrast_transducer_kulikowski( double C, double sensitivity, datmoVisualModel visual_model ) { if( visual_model & vm_contrast_masking ) { float Mt = std::min( 0.99, 1./sensitivity ); // threshold as Michelson contrast float Gt = 0.5 * log10( (Mt+1)/(1-Mt) ); // threshold as log contrast return sign(C) * powf( abs(C)-Gt+1, 0.5 ); } else { return C * sensitivity; } } double contrast_transducer( double C, double sensitivity, datmoVisualModel visual_model ) { if( visual_model & vm_contrast_masking ) { if( 0 ) // TODO: research this topic some more return contrast_transducer_kulikowski( C, sensitivity, visual_model ); else { const double W = pow( 10, fabs(C) ) - 1.; const double Q = 3., A = 3.291, B = 3.433, E = 0.8, k=0.2599; const double SC = sensitivity*W; return sign(C) * A*(pow(1.+pow(SC,Q),1./3.)-1.)/(k*pow(B+SC,E)); } } else { return C * sensitivity; } } /** * Contrast Sensitivity Function from Daly's VDP paper * * @param rho spatial frequency in cycles per degree * @param theta spatial angle in radians * @param l_adapt luminance of adaptation * @param img_size image size given in visual degrees^2 * @param viewing_dist viewing distance in meters (default = 0.5m) * @return sensitivity */ double csf_daly( double rho, double theta, double l_adapt, double im_size, double viewing_dist = 0.5 ) { if( rho == 0 ) return 0; // To avoid singularity // Sensitivity calibration constant (from Daly's paper: 250) const double P = 250.f; const double eps = 0.9; const double i_pow2 = im_size; const double l = l_adapt; const double A = 0.801 * pow(1 + 0.7 / l, -0.20); const double B = 0.3 * pow(1 + 100 / l, 0.15); const double r_a = 0.856 * powf(viewing_dist, 0.14); const double e = 0.0; // eccentricity in visual degrees const double r_e = 1.0 / (1.0 + 0.24 * e); const double r_a_r_e = r_a * r_e; double S1, S2; double B1 = B*eps*rho; S1 = pow(pow(3.23 * pow(rho*rho * i_pow2, -0.3), 5.0) + 1.0, -0.2)* A*eps*rho * exp(-B1) * sqrt(1 + 0.06*exp(B1)); const double ob=0.78; const double r_theta = (1-ob)/2 * cosf(4.0 * theta) + (1+ob)/2; rho = rho / (r_a_r_e * r_theta); B1 = B*eps*rho; S2 = powf(pow(3.23 * pow(rho*rho * i_pow2, -0.3), 5.0) + 1.0, -0.2)* A*eps*rho * exp(-B1) * sqrt(1 + 0.06*exp(B1)); return (S1 > S2 ? S2 : S1) * P; } double csf_hdrvdp( double rho, double l_adapt ) { static const float csf_pars[][5] = { { 0.0160737, 0.991265, 3.74038, 0.50722, 4.46044 }, { 0.383873, 0.800889, 3.54104, 0.682505, 4.94958 }, { 0.929301, 0.476505, 4.37453, 0.750315, 5.28678 }, { 1.29776, 0.405782, 4.40602, 0.935314, 5.61425 }, { 1.49222, 0.334278, 3.79542, 1.07327, 6.4635 }, { 1.46213, 0.394533, 2.7755, 1.16577, 7.45665 } }; return 0; } double csf_datmo( double rho, double l_adapt, datmoVisualModel visual_model ) { if( !(visual_model & vm_luminance_masking ) ) l_adapt = 1000.; if( !(visual_model & vm_csf ) ) rho = 4.; return csf_daly( rho, 0, l_adapt, 1 ); } void compute_y( double *y, const gsl_vector *x, int *skip_lut, int x_count, int L, double Ld_min, double Ld_max ) { double sum_d = 0; double alpha = 1; for( int k=0; k < L; k++ ) sum_d += gsl_vector_get( x, k ); double cy = log10(Ld_min) + alpha*(log10(Ld_max)-log10(Ld_min) - sum_d); double dy; y[0] = cy; dy = 0; for( int i=0; i < x_count-1; i++ ) { if( skip_lut[i] != -1 ) { // Check how many nodes spans this d_i int j; for( j = i+1; j < (x_count-1) && skip_lut[j] == -1; j++ ); if( j == (x_count-1) ) { // The last node dy = 0; y[i] = cy; cy += gsl_vector_get( x, skip_lut[i] ); continue; } else dy = gsl_vector_get( x, skip_lut[i] ) / (double)(j-i); } y[i] = cy; cy += dy; } y[x_count-1] = cy; } // =============== Tone mapping ============== /** * Solve the quadratic programming problem to find the optimal tone * curve for the given conditional denstity structure. * * @param y output luminance value for the nodes C->x_scale. y must be * a pre-allocated array and has the same size as C->x_scale. */ int optimize_tonecurve( datmoConditionalDensity *C_pub, DisplayFunction *dm, DisplaySize *ds, float enh_factor, double *y, const float white_y, pfstmo_progress_callback progress_cb = NULL, datmoVisualModel visual_model = vm_full, double scene_l_adapt = 1000 ) { conditional_density *C = (conditional_density*)C_pub; double d_dr = log10( dm->display( 1.f )/dm->display( 0.f ) ); // display dynamic range // Create LUTs for CSF to speed up computations UniformArrayLUT *csf_lut = new UniformArrayLUT[C->f_count]; for( int f = 0; f < C->f_count; f++ ) { csf_lut[f] = UniformArrayLUT( C->x_count, C->x_scale ); for( int i=0; i < C->x_count; i++ ) csf_lut[f].y_i[i] = csf_datmo( C->f_scale[f], pow( 10., C->x_scale[i] ), visual_model ); } const int max_neigh = (C->g_count-1)/2; // count number of needed equations and remove not used variables // Create a structure of disconnected frameworks, which can be connected later int k = 0; int skip_lut[C->x_count-1]; // LUT used to skip unused nodes int used_var[C->x_count-1]; // Used / unused variables memset( used_var, 0, sizeof( int )*(C->x_count-1) ); int minmax_i[2] = { C->x_count-1, 0 }; for( int f=0; f < C->f_count; f++ ) for( int i=0; i < C->x_count; i++ ) for( int j = std::max(0,i-max_neigh); j < std::min(C->x_count-1,i+max_neigh); j++ ) { if( i == j || (*C)(i,j-i+max_neigh,f) == 0 ) continue; k++; const int from = std::min(i,j); const int to = std::max(i,j); size_t use_fwrk = 0; for( int l = from; l <= to-1; l++ ) { used_var[l] = 1; } minmax_i[0] = std::min( minmax_i[0], from ); minmax_i[1] = std::max( minmax_i[1], to-1 ); } int white_i; if( white_y > 0 ) { k++; const float white_l = log10( white_y ); // find i that corresponds to the reference white int i; for( i = C->x_count-1; i >= 0; i-- ) if( C->x_scale[i] <= white_l ) break; white_i = i; used_var[white_i] = 1; minmax_i[0] = std::min( minmax_i[0], white_i ); minmax_i[1] = std::max( minmax_i[1], white_i ); } // Create skip lookup table (to remove selected columns that contain all zeros) int i = 0; int fwrk = 0; // Number if missing contrast ranges for( int l = 0; l < C->x_count-1; l++ ) { if( l < minmax_i[0] || l > minmax_i[1] ) { skip_lut[l] = -1; continue; } if( !used_var[l] ) { if( l>0 && !used_var[l-1] ) { skip_lut[l] = -1; continue; } fwrk++; } skip_lut[l] = i++; } const int M = k+fwrk; // number of equations const int L = i; // Number of non-zero d_i variables // Constraints // all intervals must be >=0 // sum of intervals must be equal to a displayable dynamic range // Ale = [eye(interval_count); -ones(1,interval_count)]; auto_matrix Ale(gsl_matrix_calloc(L+1, L)); gsl_matrix_set_identity( Ale ); gsl_matrix_view lower_row = gsl_matrix_submatrix( Ale, L, 0, 1, L ); gsl_matrix_set_all( &lower_row.matrix, -1 ); // ble = [zeros(interval_count,1); -d_dr]; auto_vector ble(gsl_vector_calloc(L+1)); gsl_vector_set( ble, L, -d_dr ); auto_matrix A(gsl_matrix_calloc(M, L)); auto_vector B(gsl_vector_alloc(M)); auto_vector N(gsl_vector_alloc(M)); size_t band[M]; // Frequency band (index) size_t back_x[M]; // Background luminance (index) k = 0; for( int f=0; f < C->f_count; f++ ) { double sensitivity; if( scene_l_adapt != -1 ) sensitivity = csf_datmo( C->f_scale[f], scene_l_adapt, visual_model ); for( int i=0; i < C->x_count; i++ ) for( int j = std::max(0,i-max_neigh); j < std::min(C->x_count-1,i+max_neigh); j++ ) { if( i == j || (*C)(i,j-i+max_neigh,f) == 0 ) continue; const int from = std::min(i,j); const int to = std::max(i,j); // A(k,min(i,j):(max(i,j)-1)) = 1; for( int l = from; l <= to-1; l++ ) { if( skip_lut[l] == -1 ) continue; gsl_matrix_set( A, k, skip_lut[l], 1 ); } if( scene_l_adapt == -1 ) { sensitivity = csf_lut[f].interp( C->x_scale[from] ); } // B(k,1) = l_scale(max(i,j)) - l_scale(min(i,j)); gsl_vector_set( B, k, contrast_transducer( (C->x_scale[to] - C->x_scale[from])*enh_factor, sensitivity, visual_model ) ); // N(k,k) = jpf(j-i+max_neigh+1,i,band); gsl_vector_set( N, k, (*C)(i,j-i+max_neigh,f) ); band[k] = f; back_x[k] = i; k++; } } if( white_y > 0 ) { for( int l = white_i; l < C->x_count-1; l++ ) { if( skip_lut[l] == -1 ) continue; gsl_matrix_set( A, k, skip_lut[l], 1 ); } gsl_vector_set( B, k, 0 ); gsl_vector_set( N, k, C->total * 0.1 ); // Strength of reference white anchoring band[k] = 0; back_x[k] = white_i; k++; } // Connect disconnected frameworks // This is the case when there is no contrast between some patches in an image { for( int i = minmax_i[0]; i <= minmax_i[1]; i++ ) { if( !used_var[i] ) { const int from = i; int to = i+1; while( !used_var[to] ) to++; assert( k < M ); for( int l = from; l <= to-1; l++ ) { if( skip_lut[l] == -1 ) continue; gsl_matrix_set( A, k, skip_lut[l], 1 ); } double sensitivity; if( scene_l_adapt == -1 ) { sensitivity = csf_lut[C->f_count-1].interp( C->x_scale[from] ); } else sensitivity = csf_datmo( C->f_scale[C->f_count-1], scene_l_adapt, visual_model ); // const double sensitivity = csf_datmo( C->f_scale[C->f_count-1], scene_l_adapt, visual_model ); gsl_vector_set( B, k, contrast_transducer( (C->x_scale[to] - C->x_scale[from])*enh_factor, sensitivity, visual_model ) ); gsl_vector_set( N, k, C->total * 0.1 ); // Strength of framework anchoring band[k] = C->f_count-1; back_x[k] = to; k++; i = to; } } } auto_matrix H(gsl_matrix_alloc(L, L)); auto_vector f(gsl_vector_alloc(L)); auto_matrix NA(gsl_matrix_alloc(M, L)); auto_matrix AK(gsl_matrix_alloc(M, L)); auto_vector Ax(gsl_vector_alloc(M)); auto_vector K(gsl_vector_alloc(M)); auto_vector x(gsl_vector_alloc(L)); auto_vector x_old(gsl_vector_alloc(L)); gsl_vector_set_all( x, d_dr/L ); int max_iter = 200; if( !(visual_model & vm_contrast_masking) ) max_iter = 1; for( int it = 0; it < max_iter; it++ ) { // fprintf( stderr, "Iteration #%d\n", it ); // Compute y values for the current solution compute_y( y, x, skip_lut, C->x_count, L, dm->display(0), dm->display(1) ); // Ax = A*x gsl_blas_dgemv( CblasNoTrans, 1, A, x, 0, Ax ); // T(rng{band}) = cont_transd( Ax(rng{band}), band, DD(rng{band},:)*y' ) ./ Axd(rng{band}); for( int k=0; k < M; k++ ){ const double Ax_k = gsl_vector_get( Ax, k ); const double denom = (fabs(Ax_k) < 0.0001 ? 1. : Ax_k ); // if( !(visual_model & vm_contrast_masking) ) { // gsl_vector_set( K, k, 1 ); // } else { double sensitivity = csf_lut[band[k]].interp( y[back_x[k]] ); gsl_vector_set( K, k, contrast_transducer( Ax_k, sensitivity, visual_model ) / denom ); // } } // AK = A*K; mult_rows( A, K, AK ); // NA = N*A; mult_rows( AK, N, NA ); // H = AK'*NA; gsl_blas_dgemm( CblasTrans, CblasNoTrans, 1, AK, NA, 0, H ); // f = -B'*NA = - NA' * B; gsl_blas_dgemv( CblasTrans, -1, NA, B, 0, f ); gsl_vector_memcpy( x_old, x ); solve( H, f, Ale, ble, x ); // Check for convergence double min_delta = (C->x_scale[1]-C->x_scale[0])/10.; // minimum acceptable change bool converged = true; for( int i=0; i < L; i++ ) { double delta = fabs( gsl_vector_get(x,i) - gsl_vector_get(x_old,i) ); if( delta > min_delta ) { converged = false; break; } } if( converged ) break; } if( progress_cb != NULL ) { progress_cb( 95 ); } // for( int i=0; i < L; i++ ) // fprintf( stderr, "%9.6f ", gsl_vector_get( x, i ) ); // fprintf( stderr, "\n" ); compute_y( y, x, skip_lut, C->x_count, L, dm->display(0), dm->display(1) ); delete [] csf_lut; return PFSTMO_OK; } int datmo_tonemap( float *R_out, float *G_out, float *B_out, int width, int height, const float *R_in, const float *G_in, const float *B_in, const float *L_in, DisplayFunction *df, DisplaySize *ds, const float enh_factor, const float saturation_factor, const float white_y, pfstmo_progress_callback progress_cb ) { std::auto_ptr C(datmo_compute_conditional_density( width, height, L_in, progress_cb )); datmoToneCurve tc; int res; res = datmo_compute_tone_curve( &tc, C.get(), df, ds, enh_factor, white_y, progress_cb ); datmo_apply_tone_curve_cc( R_out, G_out, B_out, width, height, R_in, G_in, B_in, L_in, &tc, df, saturation_factor ); if( progress_cb != NULL ) { progress_cb( 100 ); } return PFSTMO_OK; } int datmo_compute_tone_curve( datmoToneCurve *tc, datmoConditionalDensity *cond_dens, DisplayFunction *df, DisplaySize *ds, const float enh_factor, const float white_y, pfstmo_progress_callback progress_cb, datmoVisualModel visualModel, double scene_l_adapt ) { conditional_density *C = (conditional_density*)cond_dens; tc->init( C->x_count, C->x_scale ); return optimize_tonecurve( cond_dens, df, ds, enh_factor, tc->y_i, white_y, progress_cb, visualModel, scene_l_adapt ); } int datmo_apply_tone_curve( float *R_out, float *G_out, float *B_out, int width, int height, const float *R_in, const float *G_in, const float *B_in, const float *L_in, datmoToneCurve *tc, DisplayFunction *df, const float saturation_factor ) { // Create LUT: log10( lum factor ) -> pixel value UniformArrayLUT tc_lut( tc->size, tc->x_i ); for( int i=0; i < tc->size; i++ ) { tc_lut.y_i[i] = df->inv_display( (float)pow( 10, tc->y_i[i] ) ); } const size_t pix_count = width*height; for( size_t i=0; i < pix_count; i++ ) { float L_fix = clamp_channel(L_in[i]); const float luma = tc_lut.interp( log10(L_fix) ); R_out[i] = pow( clamp_channel(R_in[i]/L_fix), saturation_factor ) * luma; G_out[i] = pow( clamp_channel(G_in[i]/L_fix), saturation_factor ) * luma; B_out[i] = pow( clamp_channel(B_in[i]/L_fix), saturation_factor ) * luma; } return PFSTMO_OK; } /** * Apply tone curve with color correction (http://zgk.wi.ps.pl/color_correction/) */ int datmo_apply_tone_curve_cc( float *R_out, float *G_out, float *B_out, int width, int height, const float *R_in, const float *G_in, const float *B_in, const float *L_in, datmoToneCurve *tc, DisplayFunction *df, const float saturation_factor ) { // Create LUT: log10( lum factor ) -> pixel value UniformArrayLUT tc_lut( tc->size, tc->x_i ); for( int i=0; i < tc->size; i++ ) { tc_lut.y_i[i] = (float)pow( 10, tc->y_i[i] ); // tc_lut.y_i[i] = df->inv_display( (float)pow( 10, tc->y_i[i] ) ); } // Create LUT: log10( lum factor ) -> saturation correction (for the tone-level) UniformArrayLUT cc_lut( tc->size, tc->x_i ); for( int i=0; i < tc->size-1; i++ ) { const float contrast = std::max( (tc->y_i[i+1]-tc->y_i[i])/(tc->x_i[i+1]-tc->x_i[i]), 0. ); const float k1 = 1.48; const float k2 = 0.82; cc_lut.y_i[i] = ( (1 + k1)*pow(contrast,k2) )/( 1 + k1*pow(contrast,k2) ) * saturation_factor; } cc_lut.y_i[tc->size-1] = 1; const size_t pix_count = width*height; for( size_t i=0; i < pix_count; i++ ) { float L_fix = clamp_channel(L_in[i]); const float L_out = tc_lut.interp( log10(L_fix) ); const float s = cc_lut.interp( log10(L_fix) ); // color correction R_out[i] = df->inv_display(powf(clamp_channel(R_in[i]/L_fix), s) * L_out); G_out[i] = df->inv_display(powf(clamp_channel(G_in[i]/L_fix), s) * L_out); B_out[i] = df->inv_display(powf(clamp_channel(B_in[i]/L_fix), s) * L_out); } return PFSTMO_OK; } // =============== Tone-curve filtering ============== datmoToneCurve::datmoToneCurve() : x_i( NULL ), y_i( NULL ), own_y_i( false ) { } datmoToneCurve::~datmoToneCurve() { free(); } void datmoToneCurve::init( size_t n_size, const double *n_x_i, double *n_y_i ) { free(); size = n_size; x_i = n_x_i; if( n_y_i == NULL ) { y_i = new double[size]; own_y_i = true; } else { y_i = n_y_i; own_y_i = false; } } void datmoToneCurve::free() { if( y_i != NULL && own_y_i ) delete []y_i; } #if 0 // Depreciated #define DATMO_TF_TAPSIZE 26 /* Number of samples required for the temporal filter */ // Pre-computed FIR filter double t_filter[DATMO_TF_TAPSIZE] = { 0.0040, 0.0051, 0.0079, 0.0126, 0.0190, 0.0269, 0.0359, 0.0455, 0.0549, 0.0635, 0.0706, 0.0757, 0.0784, 0.0784, 0.0757, 0.0706, 0.0635, 0.0549, 0.0455, 0.0359, 0.0269, 0.0190, 0.0126, 0.0079, 0.0051, 0.0040 }; void datmo_filter_tone_curves( datmoToneCurve **in_tc, size_t count_in_tc, datmoToneCurve *out_tc ) { for( int j=0; j < in_tc[0]->size; j++ ) out_tc->y_i[j] = 0; for( int t=0; t < DATMO_TF_TAPSIZE; t++ ) { int at = t; if( at >= count_in_tc ) at = count_in_tc-1; for( int j=0; j < in_tc[0]->size; j++ ) out_tc->y_i[j] += t_filter[t] * in_tc[at]->y_i[j]; } } #endif // Pre-computed IIR filters - for different frame rates double t_filter_a_25fps[] = { 1.000000000000000, -2.748835809214676, 2.528231219142559, -0.777638560238080 }; double t_filter_b_25fps[] = { 0.000219606211225409, 0.000658818633676228, 0.000658818633676228, 0.000219606211225409 }; double t_filter_a_30fps[] = { 1.000000000000000, -2.790655305284069, 2.602653173508124, -0.810960871907291 }; double t_filter_b_30fps[] = { 0.000129624539595474, 0.000388873618786423, 0.000388873618786423, 0.000129624539595474 }; double t_filter_a_60fps[] = { 1.000000000000000, -2.895292177877897, 2.795994584283360, -0.900566088981622 }; double t_filter_b_60fps[] = { 0.0000170396779801130, 0.0000511190339403389, 0.0000511190339403389, 0.0000170396779801130 }; datmoTCFilter::datmoTCFilter( float fps, float y_min, float y_max ) : fps( fps ), y_min( y_min ), y_max( y_max ) { assert( fps == 25 || fps == 30 || fps == 60 ); if( fps == 60 ) { t_filter_a = t_filter_a_60fps; t_filter_b = t_filter_b_60fps; } else if( fps == 30 ) { t_filter_a = t_filter_a_30fps; t_filter_b = t_filter_b_30fps; } else { t_filter_a = t_filter_a_25fps; t_filter_b = t_filter_b_25fps; } pos = -1; sz = 0; } datmoToneCurve *datmoTCFilter::getToneCurvePtr() { pos++; if( pos == DATMO_TF_TAPSIZE ) pos = 0; sz++; if( sz > DATMO_TF_TAPSIZE ) sz = DATMO_TF_TAPSIZE; return ring_buffer_org + pos; } datmoToneCurve *datmoTCFilter::filterToneCurve() { datmoToneCurve *tc_o = ring_buffer_org + pos; datmoToneCurve *tc_f = ring_buffer_filt + pos; tc_f->init( tc_o->size, tc_o->x_i ); if( tc_filt_clamp.x_i == NULL ) tc_filt_clamp.init( tc_o->size, tc_o->x_i ); for( int j=0; j < tc_f->size; j++ ) tc_f->y_i[j] = 0; for( int tt=0; tt < DATMO_TF_TAPSIZE; tt++ ) { datmoToneCurve *x = get_tc(ring_buffer_org,tt); datmoToneCurve *y; if( tt >= sz ) y = x; else y = get_tc(ring_buffer_filt,tt); for( int j=0; j < tc_f->size; j++ ) { tc_f->y_i[j] += t_filter_b[tt] * x->y_i[j]; if( tt > 0 ) tc_f->y_i[j] -= t_filter_a[tt] * y->y_i[j]; } } // Copy to dest array and clamp // Note that the clamped values cannot be used for filtering as they // would cause too much rippling for the IIR filter for( int j=0; j < tc_f->size; j++ ) { if( tc_f->y_i[j] < y_min ) { tc_filt_clamp.y_i[j] = y_min; } else if( tc_f->y_i[j] > y_max ) { tc_filt_clamp.y_i[j] = y_max; } else tc_filt_clamp.y_i[j] = tc_f->y_i[j]; } return &tc_filt_clamp; } datmoToneCurve *datmoTCFilter::get_tc( datmoToneCurve *ring_buf, int time ) { if( time >= sz ) time = sz-1; int p = pos - time; if( p < 0 ) p = p + DATMO_TF_TAPSIZE; return ring_buf + p; } pfstools-2.2.0/src/tmo/mantiuk08/#display_adaptive_tmo.cpp#0000664000701400070140000010774714105165616022324 0ustar rkm38rkm38/** * @Brief Display Adaptive TMO * * From: * Rafal Mantiuk, Scott Daly, Louis Kerofsky. * Display Adaptive Tone Mapping. * To appear in: ACM Transactions on Graphics (Proc. of SIGGRAPH'08) 27 (3) * http://www.mpi-inf.mpg.de/resources/hdr/datmo/ * * This file is a part of PFSTMO package. * ---------------------------------------------------------------------- * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Rafal Mantiuk, * * $Id: display_adaptive_tmo.cpp,v 1.26 2013/12/28 14:00:54 rafm Exp $ */ #include #include #include #include #include #include #include #include "display_adaptive_tmo.h" #include #include #include #include #include "cqp/gsl_cqp.h" #include // Computing Conditional Density takes about 90% of the time #define PROGRESS_CDF 90 #define MIN_PHVAL 1e-8f // Minimum value allowed in HDR images #define MAX_PHVAL 1e8f // Maximum value allowed in HDR images /** * Simple RAII wrapper for gsl_matrix */ class auto_matrix { gsl_matrix *m; public: auto_matrix(gsl_matrix *m): m(m) {} ~auto_matrix() { gsl_matrix_free(m); } operator gsl_matrix* () { return m; } }; /** * Simple RAII wrapper for gsl_vector */ class auto_vector { gsl_vector *v; public: auto_vector(gsl_vector *v): v(v) {} ~auto_vector() { gsl_vector_free(v); } operator gsl_vector* () { return v; } }; /** * Simple RAII wrapper for gsl_cqpminimizer */ class auto_cqpminimizer { gsl_cqpminimizer *v; public: auto_cqpminimizer(gsl_cqpminimizer *v): v(v) {} ~auto_cqpminimizer() { gsl_cqpminimizer_free(v); } operator gsl_cqpminimizer* () { return v; } }; // =============== Utils ============== #define round_int( x ) (int)((x) + 0.5) #define sign( x ) ( (x)<0 ? -1 : 1 ) inline float safe_log10( float x, const float min_x = MIN_PHVAL, const float max_x = MAX_PHVAL ) { if( x < min_x ) return log10(min_x); else if( x > max_x ) return log10(max_x); else return log10( x ); } /** * Find the lowest non-zero value. Used to avoid log10(0). */ float min_positive( const float *x, size_t len ) { float min_val = MAX_PHVAL; for( int k=0; k < len; k++ ) if( unlikely(x[k] < min_val && x[k] > 0) ) min_val = x[k]; return min_val; } void mult_rows( const gsl_matrix *A, const gsl_vector *b, gsl_matrix *C ) { assert( A->size1 == b->size ); for( int j=0; j < A->size2; j++ ) for( int i=0; i < A->size1; i++ ) gsl_matrix_set( C, i, j, gsl_matrix_get(A,i,j)*gsl_vector_get(b,i) ); } /** * Lookup table on a uniform array & interpolation * * x_i must be at least two elements * y_i must be initialized after creating an object */ class UniformArrayLUT { const double *x_i; size_t lut_size; double delta; bool own_y_i; public: double *y_i; UniformArrayLUT( size_t lut_size, const double *x_i, double *y_i = NULL ) : x_i( x_i ), lut_size( lut_size ), delta( x_i[1]-x_i[0] ) { if( y_i == NULL ) { this->y_i = new double[lut_size]; own_y_i = true; } else { this->y_i = y_i; own_y_i = false; } } UniformArrayLUT() : x_i( 0 ), y_i(0), lut_size( 0 ), delta( 0. ) {} UniformArrayLUT(const UniformArrayLUT& other) : x_i( other.x_i ), lut_size( other.lut_size ), delta( other.delta ) { this->y_i = new double[lut_size]; own_y_i = true; memcpy(this->y_i, other.y_i, lut_size * sizeof(double)); } UniformArrayLUT& operator = (const UniformArrayLUT& other) { this->lut_size = other.lut_size; this->delta = other.delta; this->x_i = other.x_i; this->y_i = new double[lut_size]; own_y_i = true; memcpy(this->y_i, other.y_i, lut_size * sizeof(double)); return *this; } ~UniformArrayLUT() { if( own_y_i ) delete []y_i; } double interp( double x ) { const double ind_f = (x - x_i[0])/delta; const size_t ind_low = (size_t)(ind_f); const size_t ind_hi = (size_t)ceil(ind_f); if( unlikely(ind_f < 0) ) // Out of range checks return y_i[0]; if( unlikely(ind_hi >= lut_size) ) return y_i[lut_size-1]; if( unlikely(ind_low == ind_hi) ) return y_i[ind_low]; // No interpolation necessary return y_i[ind_low] + (y_i[ind_hi]-y_i[ind_low])*(ind_f-(double)ind_low); // Interpolation } }; #define PFSEOL "\x0a" static void dumpPFS( const char *fileName, const int width, const int height, float *data, const char *channelName ) { FILE *fh = fopen( fileName, "wb" ); assert( fh != NULL ); fprintf( fh, "PFS1" PFSEOL "%d %d" PFSEOL "1" PFSEOL "0" PFSEOL "%s" PFSEOL "0" PFSEOL "ENDH", width, height, channelName ); for( int y = 0; y < height; y++ ) for( int x = 0; x < width; x++ ) { fwrite( &data[x+y*width], sizeof( float ), 1, fh ); } fclose( fh ); } void compute_gaussian_level( const int width, const int height, const pfstmo::Array2D& in, pfstmo::Array2D& out, int level, pfstmo::Array2D& temp ) { float kernel_a = 0.4; const int kernel_len = 5; const int kernel_len_2 = kernel_len/2; float kernel[kernel_len] = { 0.25f - kernel_a/2, 0.25f , kernel_a, 0.25f, 0.25f - kernel_a/2 }; int step = 1<= width ) ) l = 2*width - 2 - l; sum += in_raw[r*width+l] * kernel[j]; } temp_raw[r*width+c] = sum; } } // Filter columns for( int c=0; c < width; c++ ) { for( int r=0; r < height; r++ ) { float sum = 0; for( int j=0; j< kernel_len; j++ ) { int l = (j-kernel_len_2)*step+r; if( unlikely(l < 0) ) l = -l; if( unlikely(l >= height) ) l = 2*height - 2 - l; sum += temp_raw[l*width+c] * kernel[j]; } out_raw[r*width+c] = sum; } } } inline float clamp_channel( const float v ) { return (v > MIN_PHVAL ? v : MIN_PHVAL); } void print_matrix( gsl_matrix *m ) { for( int r=0; r < m->size1; r++ ) { fprintf( stderr, "[ " ); for( int c=0; c< m->size2; c++ ) { fprintf( stderr, "%g ", gsl_matrix_get( m, r, c ) ); } fprintf( stderr, "]\n" ); } } void print_vector( gsl_vector *v ) { for( int r=0; r < v->size; r++ ) { fprintf( stderr, "[ %g\t ]\n ", gsl_vector_get( v, r ) ); } } /** Compute conditional probability density function */ // round_int( (l_max-l_min)/delta ) + 1; #define X_COUNT (round_int((8.f+8.f)/0.1) + 1) class conditional_density: public datmoConditionalDensity { public: static const double l_min, l_max, delta; static double x_scale[X_COUNT]; // input log luminance scale double *g_scale; // contrast scale double *f_scale; // frequency scale const double g_max; double total; int x_count, g_count, f_count; // Number of elements double *C; // Conditional probability function conditional_density( const float pix_per_deg = 30.f ) : g_max( 0.7f ) { x_count = X_COUNT; g_count = round_int(g_max/delta)*2 + 1; // Find freq. band < 3 cyc per deg int f; for( f=0; f<8; f++ ) { float b_freq = 0.5f*pix_per_deg/(float)(1<= 0) && (x + g*x_count + f*x_count*g_count < x_count*g_count*f_count) ); return C[x + g*x_count + f*x_count*g_count]; } }; datmoConditionalDensity::~datmoConditionalDensity() { } const double conditional_density::l_min = -8.f, conditional_density::l_max = 8.f, conditional_density::delta = 0.1f; double conditional_density::x_scale[X_COUNT] = { 0 }; // input log luminance scale std::unique_ptr datmo_compute_conditional_density( int width, int height, const float *L, pfstmo_progress_callback progress_cb ) { if( progress_cb != NULL ) { progress_cb( 0 ); } pfstmo::Array2D buf_1(width, height); pfstmo::Array2D buf_2(width, height); pfstmo::Array2D temp(width, height); std::unique_ptr C(new conditional_density()); const float thr = 0.0043; // Approx. discrimination threshold in log10 const int pix_count = width*height; pfstmo::Array2D* LP_low = &buf_1; pfstmo::Array2D* LP_high = &buf_2; const float min_val = std::max( min_positive( L, pix_count ), MIN_PHVAL ); // Compute log10 of an image for( int i=0; i < pix_count; i++ ) (*LP_high)(i) = safe_log10( L[i], min_val ); bool warn_out_of_range = false; C->total = 0; for( int f=0; ff_count; f++ ) { compute_gaussian_level( width, height, *LP_high, *LP_low, f, temp ); // For debug purposes only // char fname[20]; // sprintf( fname, "l_%d.pfs", f+1 ); // dumpPFS( fname, width, height, LP_low, "Y" ); const int gi_tp = C->g_count/2+1; const int gi_tn = C->g_count/2-1; const int gi_t = C->g_count/2; for( int i=0; i < pix_count; i++ ) { float g = (*LP_high)(i) - (*LP_low)(i); // Compute band-pass int x_i = round_int( ((*LP_low)(i) - C->l_min)/C->delta ); if( unlikely(x_i < 0 || x_i >= C->x_count) ) { warn_out_of_range = true; continue; } int g_i = round_int( (g + C->g_max) / C->delta ); if( unlikely(g_i < 0 || g_i >= C->g_count) ) continue; if( g > thr && g < C->delta/2 ) { // above the threshold + (*C)(x_i,gi_tp,f)++; } else if( g < -thr && g > -C->delta/2 ) { // above the threshold - (*C)(x_i,gi_tn,f)++; } else (*C)(x_i,g_i,f)++; } for( int i = 0; i < C->x_count; i++ ) { // Special case: flat field and no gradients if( (*C)(i,gi_t,f) == 0 ) continue; bool gradient_exist = false; for( int m=0; mg_count; m++ ) if( m != gi_t && (*C)(i,m,f) != 0 ) { gradient_exist = true; break; } if( ~gradient_exist ) { // generate some gradient data to avoid bad conditioned problem (*C)(i,gi_tp,f)++; (*C)(i,gi_tn,f)++; } // Compute C->total for( int m=0; mg_count; m++ ) if( likely(m != gi_t) ) C->total += (*C)(i,m,f); } std::swap( LP_low, LP_high ); if( progress_cb != NULL ) { progress_cb( (f+1)*PROGRESS_CDF/C->f_count ); } } if( warn_out_of_range ) std::cerr << "Warning: Luminance value out of permissible range\n"; // For debugging purposes only // FILE *fh = fopen( "c_dens.dat", "wt" ); // std::cerr << "x: " << C->x_count << " g: " << C->g_count << " f: " << C->f_count << "\n"; // for( int i=0; i<(C->x_count*C->g_count*C->f_count); i++ ) { // fprintf( fh, "%g\n", C->C[i] ); // } // fclose( fh ); return (std::unique_ptr)C; } // =============== Quadratic programming solver ============== const static gsl_matrix null_matrix = {0}; const static gsl_vector null_vector = {0}; /* objective function: 0.5*(x^t)Qx+(q^t)x */ /* constraints: Cx>=d */ /* Ax=b; is not used in our problem */ int solve( gsl_matrix *Q, gsl_vector *q, gsl_matrix *C, gsl_vector *d, gsl_vector *x) { gsl_cqp_data cqpd; cqpd.Q = Q; cqpd.q = q; // Do not use any equality constraints (Ax=b) // Unfortunatelly GSL does not allow for 0-size vectors and matrices // As a work-around we create a phony gsl_matrix that has 0-size. // This matrix must not be passed to any gsl function! cqpd.A = &null_matrix; cqpd.b = &null_vector; cqpd.C = C; cqpd.d = d; size_t n = cqpd.Q->size1; size_t me = cqpd.b->size; size_t mi = cqpd.d->size; const size_t max_iter = 100; size_t iter=1; int status; const gsl_cqpminimizer_type * T; T = gsl_cqpminimizer_mg_pdip; auto_cqpminimizer s(gsl_cqpminimizer_alloc(T, n, me, mi)); status = gsl_cqpminimizer_set(s, &cqpd); bool verbose = false; if( verbose ) fprintf( stderr, "== Itn ======= f ======== ||gap|| ==== ||residual||\n\n"); do { status = gsl_cqpminimizer_iterate(s); status = gsl_cqpminimizer_test_convergence(s, 1e-10, 1e-10); if( verbose ) fprintf( stderr, "%4d %14.8f %13.6e %13.6e\n", (int)iter, gsl_cqpminimizer_f(s), gsl_cqpminimizer_gap(s), gsl_cqpminimizer_residuals_norm(s)); if(status == GSL_SUCCESS) { size_t j; if( verbose ) { fprintf( stderr, "\nMinimum is found at\n"); for(j=0; jsize; j++) fprintf( stderr, "%9.6f ",gsl_vector_get(gsl_cqpminimizer_x(s), j)); fprintf( stderr, "\n\n"); } // printf("\nLagrange-multipliers for Ax=b\n"); // for(j=0; jsize; j++) // printf("%9.6f ",gsl_vector_get(gsl_cqpminimizer_lm_eq(s), j)); // printf("\n\n"); // printf("\nLagrange-multipliers for Cx>=d\n"); // for(j=0; jsize; j++) // printf("%9.6f ",gsl_vector_get(gsl_cqpminimizer_lm_ineq(s), j)); // printf("\n\n"); } else { iter++; } } while(status == GSL_CONTINUE && iter<=max_iter); bool valid_solution = true; // If the solver bahaves instable, stop at this point if( status != GSL_SUCCESS ) { if( gsl_cqp_minimizer_test_infeasibility( s, 1e-10 ) != GSL_SUCCESS ) valid_solution = false; } if( valid_solution ) gsl_vector_memcpy( x, gsl_cqpminimizer_x(s) ); return GSL_SUCCESS; } // =============== HVS functions ============== double contrast_transducer_kulikowski( double C, double sensitivity, datmoVisualModel visual_model ) { if( visual_model & vm_contrast_masking ) { float Mt = std::min( 0.99, 1./sensitivity ); // threshold as Michelson contrast float Gt = 0.5 * log10( (Mt+1)/(1-Mt) ); // threshold as log contrast return sign(C) * powf( abs(C)-Gt+1, 0.5 ); } else { return C * sensitivity; } } double contrast_transducer( double C, double sensitivity, datmoVisualModel visual_model ) { if( visual_model & vm_contrast_masking ) { if( 0 ) // TODO: research this topic some more return contrast_transducer_kulikowski( C, sensitivity, visual_model ); else { const double W = pow( 10, fabs(C) ) - 1.; const double Q = 3., A = 3.291, B = 3.433, E = 0.8, k=0.2599; const double SC = sensitivity*W; return sign(C) * A*(pow(1.+pow(SC,Q),1./3.)-1.)/(k*pow(B+SC,E)); } } else { return C * sensitivity; } } /** * Contrast Sensitivity Function from Daly's VDP paper * * @param rho spatial frequency in cycles per degree * @param theta spatial angle in radians * @param l_adapt luminance of adaptation * @param img_size image size given in visual degrees^2 * @param viewing_dist viewing distance in meters (default = 0.5m) * @return sensitivity */ double csf_daly( double rho, double theta, double l_adapt, double im_size, double viewing_dist = 0.5 ) { if( rho == 0 ) return 0; // To avoid singularity // Sensitivity calibration constant (from Daly's paper: 250) const double P = 250.f; const double eps = 0.9; const double i_pow2 = im_size; const double l = l_adapt; const double A = 0.801 * pow(1 + 0.7 / l, -0.20); const double B = 0.3 * pow(1 + 100 / l, 0.15); const double r_a = 0.856 * powf(viewing_dist, 0.14); const double e = 0.0; // eccentricity in visual degrees const double r_e = 1.0 / (1.0 + 0.24 * e); const double r_a_r_e = r_a * r_e; double S1, S2; double B1 = B*eps*rho; S1 = pow(pow(3.23 * pow(rho*rho * i_pow2, -0.3), 5.0) + 1.0, -0.2)* A*eps*rho * exp(-B1) * sqrt(1 + 0.06*exp(B1)); const double ob=0.78; const double r_theta = (1-ob)/2 * cosf(4.0 * theta) + (1+ob)/2; rho = rho / (r_a_r_e * r_theta); B1 = B*eps*rho; S2 = powf(pow(3.23 * pow(rho*rho * i_pow2, -0.3), 5.0) + 1.0, -0.2)* A*eps*rho * exp(-B1) * sqrt(1 + 0.06*exp(B1)); return (S1 > S2 ? S2 : S1) * P; } double csf_hdrvdp( double rho, double l_adapt ) { static const float csf_pars[][5] = { { 0.0160737, 0.991265, 3.74038, 0.50722, 4.46044 }, { 0.383873, 0.800889, 3.54104, 0.682505, 4.94958 }, { 0.929301, 0.476505, 4.37453, 0.750315, 5.28678 }, { 1.29776, 0.405782, 4.40602, 0.935314, 5.61425 }, { 1.49222, 0.334278, 3.79542, 1.07327, 6.4635 }, { 1.46213, 0.394533, 2.7755, 1.16577, 7.45665 } }; return 0; } double csf_datmo( double rho, double l_adapt, datmoVisualModel visual_model ) { if( !(visual_model & vm_luminance_masking ) ) l_adapt = 1000.; if( !(visual_model & vm_csf ) ) rho = 4.; return csf_daly( rho, 0, l_adapt, 1 ); } void compute_y( double *y, const gsl_vector *x, int *skip_lut, int x_count, int L, double Ld_min, double Ld_max ) { double sum_d = 0; double alpha = 1; for( int k=0; k < L; k++ ) sum_d += gsl_vector_get( x, k ); double cy = log10(Ld_min) + alpha*(log10(Ld_max)-log10(Ld_min) - sum_d); double dy; y[0] = cy; dy = 0; for( int i=0; i < x_count-1; i++ ) { if( skip_lut[i] != -1 ) { // Check how many nodes spans this d_i int j; for( j = i+1; j < (x_count-1) && skip_lut[j] == -1; j++ ); if( j == (x_count-1) ) { // The last node dy = 0; y[i] = cy; cy += gsl_vector_get( x, skip_lut[i] ); continue; } else dy = gsl_vector_get( x, skip_lut[i] ) / (double)(j-i); } y[i] = cy; cy += dy; } y[x_count-1] = cy; } // =============== Tone mapping ============== /** * Solve the quadratic programming problem to find the optimal tone * curve for the given conditional denstity structure. * * @param y output luminance value for the nodes C->x_scale. y must be * a pre-allocated array and has the same size as C->x_scale. */ int optimize_tonecurve( datmoConditionalDensity *C_pub, DisplayFunction *dm, DisplaySize *ds, float enh_factor, double *y, const float white_y, pfstmo_progress_callback progress_cb = NULL, datmoVisualModel visual_model = vm_full, double scene_l_adapt = 1000 ) { conditional_density *C = (conditional_density*)C_pub; double d_dr = log10( dm->display( 1.f )/dm->display( 0.f ) ); // display dynamic range // Create LUTs for CSF to speed up computations UniformArrayLUT *csf_lut = new UniformArrayLUT[C->f_count]; for( int f = 0; f < C->f_count; f++ ) { csf_lut[f] = UniformArrayLUT( C->x_count, C->x_scale ); for( int i=0; i < C->x_count; i++ ) csf_lut[f].y_i[i] = csf_datmo( C->f_scale[f], pow( 10., C->x_scale[i] ), visual_model ); } const int max_neigh = (C->g_count-1)/2; // count number of needed equations and remove not used variables // Create a structure of disconnected frameworks, which can be connected later int k = 0; int skip_lut[C->x_count-1]; // LUT used to skip unused nodes int used_var[C->x_count-1]; // Used / unused variables memset( used_var, 0, sizeof( int )*(C->x_count-1) ); int minmax_i[2] = { C->x_count-1, 0 }; for( int f=0; f < C->f_count; f++ ) for( int i=0; i < C->x_count; i++ ) for( int j = std::max(0,i-max_neigh); j < std::min(C->x_count-1,i+max_neigh); j++ ) { if( i == j || (*C)(i,j-i+max_neigh,f) == 0 ) continue; k++; const int from = std::min(i,j); const int to = std::max(i,j); size_t use_fwrk = 0; for( int l = from; l <= to-1; l++ ) { used_var[l] = 1; } minmax_i[0] = std::min( minmax_i[0], from ); minmax_i[1] = std::max( minmax_i[1], to-1 ); } int white_i; if( white_y > 0 ) { k++; const float white_l = log10( white_y ); // find i that corresponds to the reference white int i; for( i = C->x_count-1; i >= 0; i-- ) if( C->x_scale[i] <= white_l ) break; white_i = i; used_var[white_i] = 1; minmax_i[0] = std::min( minmax_i[0], white_i ); minmax_i[1] = std::max( minmax_i[1], white_i ); } // Create skip lookup table (to remove selected columns that contain all zeros) int i = 0; int fwrk = 0; // Number if missing contrast ranges for( int l = 0; l < C->x_count-1; l++ ) { if( l < minmax_i[0] || l > minmax_i[1] ) { skip_lut[l] = -1; continue; } if( !used_var[l] ) { if( l>0 && !used_var[l-1] ) { skip_lut[l] = -1; continue; } fwrk++; } skip_lut[l] = i++; } const int M = k+fwrk; // number of equations const int L = i; // Number of non-zero d_i variables // Constraints // all intervals must be >=0 // sum of intervals must be equal to a displayable dynamic range // Ale = [eye(interval_count); -ones(1,interval_count)]; auto_matrix Ale(gsl_matrix_calloc(L+1, L)); gsl_matrix_set_identity( Ale ); gsl_matrix_view lower_row = gsl_matrix_submatrix( Ale, L, 0, 1, L ); gsl_matrix_set_all( &lower_row.matrix, -1 ); // ble = [zeros(interval_count,1); -d_dr]; auto_vector ble(gsl_vector_calloc(L+1)); gsl_vector_set( ble, L, -d_dr ); auto_matrix A(gsl_matrix_calloc(M, L)); auto_vector B(gsl_vector_alloc(M)); auto_vector N(gsl_vector_alloc(M)); size_t band[M]; // Frequency band (index) size_t back_x[M]; // Background luminance (index) k = 0; for( int f=0; f < C->f_count; f++ ) { double sensitivity; if( scene_l_adapt != -1 ) sensitivity = csf_datmo( C->f_scale[f], scene_l_adapt, visual_model ); for( int i=0; i < C->x_count; i++ ) for( int j = std::max(0,i-max_neigh); j < std::min(C->x_count-1,i+max_neigh); j++ ) { if( i == j || (*C)(i,j-i+max_neigh,f) == 0 ) continue; const int from = std::min(i,j); const int to = std::max(i,j); // A(k,min(i,j):(max(i,j)-1)) = 1; for( int l = from; l <= to-1; l++ ) { if( skip_lut[l] == -1 ) continue; gsl_matrix_set( A, k, skip_lut[l], 1 ); } if( scene_l_adapt == -1 ) { sensitivity = csf_lut[f].interp( C->x_scale[from] ); } // B(k,1) = l_scale(max(i,j)) - l_scale(min(i,j)); gsl_vector_set( B, k, contrast_transducer( (C->x_scale[to] - C->x_scale[from])*enh_factor, sensitivity, visual_model ) ); // N(k,k) = jpf(j-i+max_neigh+1,i,band); gsl_vector_set( N, k, (*C)(i,j-i+max_neigh,f) ); band[k] = f; back_x[k] = i; k++; } } if( white_y > 0 ) { for( int l = white_i; l < C->x_count-1; l++ ) { if( skip_lut[l] == -1 ) continue; gsl_matrix_set( A, k, skip_lut[l], 1 ); } gsl_vector_set( B, k, 0 ); gsl_vector_set( N, k, C->total * 0.1 ); // Strength of reference white anchoring band[k] = 0; back_x[k] = white_i; k++; } // Connect disconnected frameworks // This is the case when there is no contrast between some patches in an image { for( int i = minmax_i[0]; i <= minmax_i[1]; i++ ) { if( !used_var[i] ) { const int from = i; int to = i+1; while( !used_var[to] ) to++; assert( k < M ); for( int l = from; l <= to-1; l++ ) { if( skip_lut[l] == -1 ) continue; gsl_matrix_set( A, k, skip_lut[l], 1 ); } double sensitivity; if( scene_l_adapt == -1 ) { sensitivity = csf_lut[C->f_count-1].interp( C->x_scale[from] ); } else sensitivity = csf_datmo( C->f_scale[C->f_count-1], scene_l_adapt, visual_model ); // const double sensitivity = csf_datmo( C->f_scale[C->f_count-1], scene_l_adapt, visual_model ); gsl_vector_set( B, k, contrast_transducer( (C->x_scale[to] - C->x_scale[from])*enh_factor, sensitivity, visual_model ) ); gsl_vector_set( N, k, C->total * 0.1 ); // Strength of framework anchoring band[k] = C->f_count-1; back_x[k] = to; k++; i = to; } } } auto_matrix H(gsl_matrix_alloc(L, L)); auto_vector f(gsl_vector_alloc(L)); auto_matrix NA(gsl_matrix_alloc(M, L)); auto_matrix AK(gsl_matrix_alloc(M, L)); auto_vector Ax(gsl_vector_alloc(M)); auto_vector K(gsl_vector_alloc(M)); auto_vector x(gsl_vector_alloc(L)); auto_vector x_old(gsl_vector_alloc(L)); gsl_vector_set_all( x, d_dr/L ); int max_iter = 200; if( !(visual_model & vm_contrast_masking) ) max_iter = 1; for( int it = 0; it < max_iter; it++ ) { // fprintf( stderr, "Iteration #%d\n", it ); // Compute y values for the current solution compute_y( y, x, skip_lut, C->x_count, L, dm->display(0), dm->display(1) ); // Ax = A*x gsl_blas_dgemv( CblasNoTrans, 1, A, x, 0, Ax ); // T(rng{band}) = cont_transd( Ax(rng{band}), band, DD(rng{band},:)*y' ) ./ Axd(rng{band}); for( int k=0; k < M; k++ ){ const double Ax_k = gsl_vector_get( Ax, k ); const double denom = (fabs(Ax_k) < 0.0001 ? 1. : Ax_k ); // if( !(visual_model & vm_contrast_masking) ) { // gsl_vector_set( K, k, 1 ); // } else { double sensitivity = csf_lut[band[k]].interp( y[back_x[k]] ); gsl_vector_set( K, k, contrast_transducer( Ax_k, sensitivity, visual_model ) / denom ); // } } // AK = A*K; mult_rows( A, K, AK ); // NA = N*A; mult_rows( AK, N, NA ); // H = AK'*NA; gsl_blas_dgemm( CblasTrans, CblasNoTrans, 1, AK, NA, 0, H ); // f = -B'*NA = - NA' * B; gsl_blas_dgemv( CblasTrans, -1, NA, B, 0, f ); gsl_vector_memcpy( x_old, x ); solve( H, f, Ale, ble, x ); // Check for convergence double min_delta = (C->x_scale[1]-C->x_scale[0])/10.; // minimum acceptable change bool converged = true; for( int i=0; i < L; i++ ) { double delta = fabs( gsl_vector_get(x,i) - gsl_vector_get(x_old,i) ); if( delta > min_delta ) { converged = false; break; } } if( converged ) break; } if( progress_cb != NULL ) { progress_cb( 95 ); } // for( int i=0; i < L; i++ ) // fprintf( stderr, "%9.6f ", gsl_vector_get( x, i ) ); // fprintf( stderr, "\n" ); compute_y( y, x, skip_lut, C->x_count, L, dm->display(0), dm->display(1) ); delete [] csf_lut; return PFSTMO_OK; } int datmo_tonemap( float *R_out, float *G_out, float *B_out, int width, int height, const float *R_in, const float *G_in, const float *B_in, const float *L_in, DisplayFunction *df, DisplaySize *ds, const float enh_factor, const float saturation_factor, const float white_y, pfstmo_progress_callback progress_cb ) { std::unique_ptr C(datmo_compute_conditional_density( width, height, L_in, progress_cb )); datmoToneCurve tc; int res; res = datmo_compute_tone_curve( &tc, C.get(), df, ds, enh_factor, white_y, progress_cb ); datmo_apply_tone_curve_cc( R_out, G_out, B_out, width, height, R_in, G_in, B_in, L_in, &tc, df, saturation_factor ); if( progress_cb != NULL ) { progress_cb( 100 ); } return PFSTMO_OK; } int datmo_compute_tone_curve( datmoToneCurve *tc, datmoConditionalDensity *cond_dens, DisplayFunction *df, DisplaySize *ds, const float enh_factor, const float white_y, pfstmo_progress_callback progress_cb, datmoVisualModel visualModel, double scene_l_adapt ) { conditional_density *C = (conditional_density*)cond_dens; tc->init( C->x_count, C->x_scale ); return optimize_tonecurve( cond_dens, df, ds, enh_factor, tc->y_i, white_y, progress_cb, visualModel, scene_l_adapt ); } int datmo_apply_tone_curve( float *R_out, float *G_out, float *B_out, int width, int height, const float *R_in, const float *G_in, const float *B_in, const float *L_in, datmoToneCurve *tc, DisplayFunction *df, const float saturation_factor ) { // Create LUT: log10( lum factor ) -> pixel value UniformArrayLUT tc_lut( tc->size, tc->x_i ); for( int i=0; i < tc->size; i++ ) { tc_lut.y_i[i] = df->inv_display( (float)pow( 10, tc->y_i[i] ) ); } const size_t pix_count = width*height; for( size_t i=0; i < pix_count; i++ ) { float L_fix = clamp_channel(L_in[i]); const float luma = tc_lut.interp( log10(L_fix) ); R_out[i] = pow( clamp_channel(R_in[i]/L_fix), saturation_factor ) * luma; G_out[i] = pow( clamp_channel(G_in[i]/L_fix), saturation_factor ) * luma; B_out[i] = pow( clamp_channel(B_in[i]/L_fix), saturation_factor ) * luma; } return PFSTMO_OK; } /** * Apply tone curve with color correction (http://zgk.wi.ps.pl/color_correction/) */ int datmo_apply_tone_curve_cc( float *R_out, float *G_out, float *B_out, int width, int height, const float *R_in, const float *G_in, const float *B_in, const float *L_in, datmoToneCurve *tc, DisplayFunction *df, const float saturation_factor ) { // Create LUT: log10( lum factor ) -> pixel value UniformArrayLUT tc_lut( tc->size, tc->x_i ); for( int i=0; i < tc->size; i++ ) { tc_lut.y_i[i] = (float)pow( 10, tc->y_i[i] ); // tc_lut.y_i[i] = df->inv_display( (float)pow( 10, tc->y_i[i] ) ); } // Create LUT: log10( lum factor ) -> saturation correction (for the tone-level) UniformArrayLUT cc_lut( tc->size, tc->x_i ); for( int i=0; i < tc->size-1; i++ ) { const float contrast = std::max( (tc->y_i[i+1]-tc->y_i[i])/(tc->x_i[i+1]-tc->x_i[i]), 0. ); const float k1 = 1.48; const float k2 = 0.82; cc_lut.y_i[i] = ( (1 + k1)*pow(contrast,k2) )/( 1 + k1*pow(contrast,k2) ) * saturation_factor; } cc_lut.y_i[tc->size-1] = 1; const size_t pix_count = width*height; for( size_t i=0; i < pix_count; i++ ) { float L_fix = clamp_channel(L_in[i]); const float L_out = tc_lut.interp( log10(L_fix) ); const float s = cc_lut.interp( log10(L_fix) ); // color correction R_out[i] = df->inv_display(powf(clamp_channel(R_in[i]/L_fix), s) * L_out); G_out[i] = df->inv_display(powf(clamp_channel(G_in[i]/L_fix), s) * L_out); B_out[i] = df->inv_display(powf(clamp_channel(B_in[i]/L_fix), s) * L_out); } return PFSTMO_OK; } // =============== Tone-curve filtering ============== datmoToneCurve::datmoToneCurve() : x_i( NULL ), y_i( NULL ), own_y_i( false ) { } datmoToneCurve::~datmoToneCurve() { free(); } void datmoToneCurve::init( size_t n_size, const double *n_x_i, double *n_y_i ) { free(); size = n_size; x_i = n_x_i; if( n_y_i == NULL ) { y_i = new double[size]; own_y_i = true; } else { y_i = n_y_i; own_y_i = false; } } void datmoToneCurve::free() { if( y_i != NULL && own_y_i ) delete []y_i; } #if 0 // Depreciated #define DATMO_TF_TAPSIZE 26 /* Number of samples required for the temporal filter */ // Pre-computed FIR filter double t_filter[DATMO_TF_TAPSIZE] = { 0.0040, 0.0051, 0.0079, 0.0126, 0.0190, 0.0269, 0.0359, 0.0455, 0.0549, 0.0635, 0.0706, 0.0757, 0.0784, 0.0784, 0.0757, 0.0706, 0.0635, 0.0549, 0.0455, 0.0359, 0.0269, 0.0190, 0.0126, 0.0079, 0.0051, 0.0040 }; void datmo_filter_tone_curves( datmoToneCurve **in_tc, size_t count_in_tc, datmoToneCurve *out_tc ) { for( int j=0; j < in_tc[0]->size; j++ ) out_tc->y_i[j] = 0; for( int t=0; t < DATMO_TF_TAPSIZE; t++ ) { int at = t; if( at >= count_in_tc ) at = count_in_tc-1; for( int j=0; j < in_tc[0]->size; j++ ) out_tc->y_i[j] += t_filter[t] * in_tc[at]->y_i[j]; } } #endif // Pre-computed IIR filters - for different frame rates double t_filter_a_25fps[] = { 1.000000000000000, -2.748835809214676, 2.528231219142559, -0.777638560238080 }; double t_filter_b_25fps[] = { 0.000219606211225409, 0.000658818633676228, 0.000658818633676228, 0.000219606211225409 }; double t_filter_a_30fps[] = { 1.000000000000000, -2.790655305284069, 2.602653173508124, -0.810960871907291 }; double t_filter_b_30fps[] = { 0.000129624539595474, 0.000388873618786423, 0.000388873618786423, 0.000129624539595474 }; double t_filter_a_60fps[] = { 1.000000000000000, -2.895292177877897, 2.795994584283360, -0.900566088981622 }; double t_filter_b_60fps[] = { 0.0000170396779801130, 0.0000511190339403389, 0.0000511190339403389, 0.0000170396779801130 }; datmoTCFilter::datmoTCFilter( float fps, float y_min, float y_max ) : fps( fps ), y_min( y_min ), y_max( y_max ) { assert( fps == 25 || fps == 30 || fps == 60 ); if( fps == 60 ) { t_filter_a = t_filter_a_60fps; t_filter_b = t_filter_b_60fps; } else if( fps == 30 ) { t_filter_a = t_filter_a_30fps; t_filter_b = t_filter_b_30fps; } else { t_filter_a = t_filter_a_25fps; t_filter_b = t_filter_b_25fps; } pos = -1; sz = 0; } datmoToneCurve *datmoTCFilter::getToneCurvePtr() { pos++; if( pos == DATMO_TF_TAPSIZE ) pos = 0; sz++; if( sz > DATMO_TF_TAPSIZE ) sz = DATMO_TF_TAPSIZE; return ring_buffer_org + pos; } datmoToneCurve *datmoTCFilter::filterToneCurve() { datmoToneCurve *tc_o = ring_buffer_org + pos; datmoToneCurve *tc_f = ring_buffer_filt + pos; tc_f->init( tc_o->size, tc_o->x_i ); if( tc_filt_clamp.x_i == NULL ) tc_filt_clamp.init( tc_o->size, tc_o->x_i ); for( int j=0; j < tc_f->size; j++ ) tc_f->y_i[j] = 0; for( int tt=0; tt < DATMO_TF_TAPSIZE; tt++ ) { datmoToneCurve *x = get_tc(ring_buffer_org,tt); datmoToneCurve *y; if( tt >= sz ) y = x; else y = get_tc(ring_buffer_filt,tt); for( int j=0; j < tc_f->size; j++ ) { tc_f->y_i[j] += t_filter_b[tt] * x->y_i[j]; if( tt > 0 ) tc_f->y_i[j] -= t_filter_a[tt] * y->y_i[j]; } } // Copy to dest array and clamp // Note that the clamped values cannot be used for filtering as they // would cause too much rippling for the IIR filter for( int j=0; j < tc_f->size; j++ ) { if( tc_f->y_i[j] < y_min ) { tc_filt_clamp.y_i[j] = y_min; } else if( tc_f->y_i[j] > y_max ) { tc_filt_clamp.y_i[j] = y_max; } else tc_filt_clamp.y_i[j] = tc_f->y_i[j]; } return &tc_filt_clamp; } datmoToneCurve *datmoTCFilter::get_tc( datmoToneCurve *ring_buf, int time ) { if( time >= sz ) time = sz-1; int p = pos - time; if( p < 0 ) p = p + DATMO_TF_TAPSIZE; return ring_buf + p; } pfstools-2.2.0/src/tmo/mantiuk08/pfstmo_mantiuk08.cpp0000664000701400070140000003014414105165616021207 0ustar rkm38rkm38/** * @brief Display Adaptive TMO * * From: * Rafal Mantiuk, Scott Daly, Louis Kerofsky. * Display Adaptive Tone Mapping. * To appear in: ACM Transactions on Graphics (Proc. of SIGGRAPH'08) 27 (3) * http://www.mpi-inf.mpg.de/resources/hdr/datmo/ * * This file is a part of PFSTMO package. * ---------------------------------------------------------------------- * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Rafal Mantiuk, * * $Id: pfstmo_mantiuk08.cpp,v 1.19 2013/12/28 14:00:54 rafm Exp $ */ #include #include #include #include #include #include #include #include #include #include #include #include "display_adaptive_tmo.h" #define PROG_NAME "pfstmo_mantiuk08" class QuietException { }; class Timing { timeval t1; public: Timing() { gettimeofday(&t1, NULL); } void report( const char *activity ) { timeval t2; gettimeofday(&t2, NULL); unsigned int t = (t2.tv_sec - t1.tv_sec) * 1000000 + (t2.tv_usec - t1.tv_usec); fprintf( stderr, "Activity %s took %g seconds.\n", activity, (float)t / 1000000.f ); } }; using namespace std; const char *temp_file_2pass = "datmo_tone_curves.tmp"; bool read_tone_curve( FILE *fh, datmoToneCurve *tc, double **x_scale ); void printHelp() { fprintf( stderr, PROG_NAME " (" PACKAGE_STRING ") : \n" "\t[--display-function ] [--display-size=]\n" "\t[--color-saturation ] [--contrast-enhancement ]\n" "\t[--white-y=]\n" "\t[--verbose] [--quiet] [--help]\n" "See man page for more information.\n" ); } bool verbose = false; bool quiet = false; #define FRAME_NAME_MAX 30 char frame_name[FRAME_NAME_MAX+1]; int progress_report( int progress ) { if( !quiet ) { fprintf( stderr, "\r'%s' completed %d%%", frame_name, progress ); if( progress == 100 ) fprintf( stderr, "\n" ); } return PFSTMO_CB_CONTINUE; } void tmo_mantiuk08(int argc, char * argv[]) { //--- default tone mapping parameters; float contrast_enhance_factor = 1.f; float saturation_factor = 1.f; float white_y = -2.f; float fps = 25; int temporal_filter = 0; int itmax = 200; float tol = 1e-3; DisplayFunction *df = NULL; DisplaySize *ds = NULL; const char *output_tc = NULL; datmoVisualModel visual_model = vm_full; double scene_l_adapt = 1000; //--- process command line args df = createDisplayFunctionFromArgs( argc, argv ); if( df == NULL ) df = new DisplayFunctionGGBA( "lcd" ); ds = createDisplaySizeFromArgs( argc, argv ); if( ds == NULL ) ds = new DisplaySize( 30.f, 0.5f ); static struct option cmdLineOptions[] = { { "help", no_argument, NULL, 'h' }, { "verbose", no_argument, NULL, 'v' }, { "contrast-enhancement", required_argument, NULL, 'e' }, { "color-saturation", required_argument, NULL, 'c' }, { "white-y", required_argument, NULL, 'y' }, { "fps", required_argument, NULL, 'f' }, { "output-tone-curve", required_argument, NULL, 'o' }, { "visual-model", required_argument, NULL, 'm' }, { "scene-y-adapt", required_argument, NULL, 'a' }, { "quiet", no_argument, NULL, 'q' }, { NULL, 0, NULL, 0 } }; int optionIndex = 0; while( 1 ) { int c = getopt_long (argc, argv, "vhe:c:y:t:o:qm:f:a:", cmdLineOptions, &optionIndex); if( c == -1 ) break; switch( c ) { case 'h': printHelp(); throw QuietException(); case 'v': verbose = true; break; case 'q': quiet = true; break; case 'a': if( !strcmp( optarg, "auto" ) ) scene_l_adapt = -1; else { scene_l_adapt = (float)strtod( optarg, NULL ); if( scene_l_adapt <= 0.0f ) throw pfs::Exception("incorrect scane adaptation luminance. The value must be greater than 0."); } break; case 'e': contrast_enhance_factor = (float)strtod( optarg, NULL ); if( contrast_enhance_factor <= 0.0f ) throw pfs::Exception("incorrect contrast enhancement factor, accepted value must be a positive number"); break; case 'c': saturation_factor = (float)strtod( optarg, NULL ); if( saturation_factor < 0.0f || saturation_factor > 2.0f ) throw pfs::Exception("incorrect saturation factor, accepted range is (0..2)"); break; case 'm': { char *saveptr; char *token; visual_model = vm_none; token = strtok_r( optarg, ",:", &saveptr ); while( token != NULL ) { if( !strcmp( token, "none" ) ) { visual_model = vm_none; } else if( !strcmp( token, "full" ) ) { visual_model = vm_full; } else if( !strcmp( token, "luminance_masking" ) ) { visual_model |= vm_luminance_masking; } else if( !strcmp( token, "contrast_masking" ) ) { visual_model |= vm_contrast_masking; } else if( !strcmp( token, "csf" ) ) { visual_model |= vm_csf; } else throw pfs::Exception("Unrecognized visual model"); token = strtok_r( NULL, ",:", &saveptr ); } } break; case 'y': if( !strcmp( optarg, "none" ) ) white_y = -1; else white_y = (float)strtod( optarg, NULL ); if( white_y < 0.0f ) throw pfs::Exception("incorrect white-y value. The value must be greater than 0"); break; case 'f': fps = strtod( optarg, NULL ); if( fps != 25 && fps != 30 && fps != 60 ) throw pfs::Exception("Only 3 frame-per-seconds values are supported: 25, 30 and 60."); break; case 'o': output_tc = optarg; break; case '?': throw QuietException(); case ':': throw QuietException(); } } if( verbose ) { df->print( stderr ); ds->print( stderr ); fprintf( stderr, "Frames-per-second: %g\n", fps ); fprintf( stderr, "Contrast masking: %d\n", (bool)(visual_model & vm_contrast_masking) ); fprintf( stderr, "Luminance masking: %d\n", (bool)(visual_model & vm_luminance_masking) ); fprintf( stderr, "CSF: %d\n", (bool)(visual_model & vm_csf) ); fprintf( stderr, "Scane adaptation luminance: %g (-1 means auto)\n", scene_l_adapt ); } Timing tm_entire; FILE *tc_FH = NULL; if( output_tc != NULL ) { tc_FH = fopen( output_tc, "w" ); if( tc_FH == NULL ) throw pfs::Exception("cannot open file for writing tone-curve."); } datmoTCFilter rc_filter( fps, log10(df->display(0)), log10(df->display(1)) ); pfs::DOMIO pfsio; size_t frame_no = 0; while( true ) { pfs::Frame *frame = pfsio.readFrame( stdin ); if( frame == NULL ) break; pfs::Channel *inX, *inY, *inZ; frame->getXYZChannels(inX, inY, inZ); int cols = frame->getWidth(); int rows = frame->getHeight(); const char *file_name = frame->getTags()->getString( "FILE_NAME" ); if( file_name == NULL ) sprintf( frame_name, "frame #%d", (int)frame_no ); else { int len = strlen( file_name ); if( len > FRAME_NAME_MAX ) // In case file name is too long len = FRAME_NAME_MAX-3; strcpy( frame_name, "..." ); strncpy( frame_name+3, file_name + strlen( file_name ) - len, len+1 ); } { pfs::Array2DImpl R( cols, rows ); pfs::transformColorSpace( pfs::CS_XYZ, inX, inY, inZ, pfs::CS_RGB, inX, &R, inZ ); if( white_y == -2.f ) { // If not overriden by command line options const char *white_y_str = frame->getTags()->getString( "WHITE_Y" ); if( white_y_str != NULL ) { white_y = strtof( white_y_str, NULL ); if( white_y == 0 ) { white_y = -1; fprintf( stderr, PROG_NAME ": warning - wrong WHITE_Y in the input image" ); } } } if( verbose && frame_no == 0 ) { fprintf( stderr, "Luminance factor of the reference white: " ); if( white_y < 0 ) fprintf( stderr, "not specified\n" ); else fprintf( stderr, "%g\n", white_y ); } const char *lum_data = frame->getTags()->getString("LUMINANCE"); if( lum_data != NULL && !strcmp( lum_data, "DISPLAY" ) && frame_no == 0 ) fprintf( stderr, PROG_NAME " warning: input image should be in linear (not gamma corrected) luminance factor units. Use '--linear' option with pfsin* commands.\n" ); Timing tm_cond_dens; std::auto_ptr C = datmo_compute_conditional_density( cols, rows, inY->getRawData(), progress_report ); if( C.get() == NULL ) throw pfs::Exception("failed to analyse the image"); tm_cond_dens.report( "Conditional density" ); Timing tm_comp_tone_curve; int res; datmoToneCurve *tc = rc_filter.getToneCurvePtr(); res = datmo_compute_tone_curve( tc, C.get(), df, ds, contrast_enhance_factor, white_y, progress_report, visual_model, scene_l_adapt ); if( res != PFSTMO_OK ) throw pfs::Exception( "failed to compute a tone-curve" ); datmoToneCurve *tc_filt = rc_filter.filterToneCurve(); tm_comp_tone_curve.report( "Computing a tone-cuve" ); { Timing tm_tonecurve; int res; res = datmo_apply_tone_curve_cc( inX->getRawData(), R.getRawData(), inZ->getRawData(), cols, rows, inX->getRawData(), R.getRawData(), inZ->getRawData(), inY->getRawData(), tc_filt, df, saturation_factor ); if( res != PFSTMO_OK ) throw pfs::Exception( "failed to tone-map an image" ); tm_tonecurve.report( "Apply tone-curve" ); if( tc_FH != NULL ) { for( int i=0; i < tc_filt->size; i++ ) fprintf( tc_FH, "%d,%g,%g,%g\n", (int)frame_no, tc_filt->x_i[i], tc_filt->y_i[i], df->inv_display( (float)pow( 10, tc_filt->y_i[i] ) ) ); } // int res; // res = datmo_tonemap( inX->getRawData(), R.getRawData(), inZ->getRawData(), cols, rows, // inX->getRawData(), R.getRawData(), inZ->getRawData(), inY->getRawData(), // df, ds, contrast_enhance_factor, saturation_factor, white_y, progress_report ); progress_report( 100 ); pfs::transformColorSpace( pfs::CS_RGB, inX, &R, inZ, pfs::CS_XYZ, inX, inY, inZ ); frame->getTags()->setString("LUMINANCE", "DISPLAY"); pfsio.writeFrame( frame, stdout ); } pfsio.freeFrame(frame); } frame_no++; } if( tc_FH != NULL ) fclose( tc_FH ); delete df; delete ds; tm_entire.report( "Entire operation" ); } bool read_tone_curve( FILE *fh, datmoToneCurve *tc, double **x_scale ) { int size, frame_no, read; read = fscanf( fh, "%d,%d\n", &frame_no, &size ); if( read != 2 ) return false; if( *x_scale == NULL ) *x_scale = new double[size]; tc->init( size, *x_scale ); for( int i=0; i < size; i++ ) { float x, y; read = fscanf( fh, "%f,%f\n", &x, &y ); if( read != 2 ) throw pfs::Exception( "missing data in the 2-pass file" ); (*x_scale)[i] = x; tc->y_i[i] = y; } return true; } int main( int argc, char* argv[] ) { try { tmo_mantiuk08( argc, argv ); } catch( pfs::Exception ex ) { fprintf( stderr, PROG_NAME " error: %s\n", ex.getMessage() ); return EXIT_FAILURE; } catch( QuietException ex ) { return EXIT_FAILURE; } return EXIT_SUCCESS; } pfstools-2.2.0/src/tmo/mantiuk08/display_adaptive_tmo.h0000664000701400070140000002503014105165616021643 0ustar rkm38rkm38/** * @brief Display Adaptive TMO * * From: * Rafal Mantiuk, Scott Daly, Louis Kerofsky. * Display Adaptive Tone Mapping. * To appear in: ACM Transactions on Graphics (Proc. of SIGGRAPH'08) 27 (3) * http://www.mpi-inf.mpg.de/resources/hdr/datmo/ * * This file is a part of PFSTMO package. * ---------------------------------------------------------------------- * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Rafal Mantiuk, * * $Id: display_adaptive_tmo.h,v 1.16 2013/12/28 14:00:54 rafm Exp $ */ #include #include "display_function.h" #include "display_size.h" #include "pfstmo.h" #define DATMO_TF_TAPSIZE 4 /* Number of samples required for the temporal filter */ class datmoToneCurve { bool own_y_i; public: size_t size; const double *x_i; /* log10 of input luminance factor */ double *y_i; /* log10 of output luminance (use inverse display model to get pixel values) */ datmoToneCurve(); ~datmoToneCurve(); void init( size_t n_size, const double *n_x_i, double *n_y_i = NULL ); void free(); }; class datmoTCFilter { int pos, sz; float y_min, y_max, fps; double *t_filter_a, *t_filter_b; datmoToneCurve ring_buffer_org[DATMO_TF_TAPSIZE]; // original datmoToneCurve ring_buffer_filt[DATMO_TF_TAPSIZE]; // filtered datmoToneCurve tc_filt_clamp; // filtered and clammped tone-curve datmoToneCurve *get_tc( datmoToneCurve *ring_buf, int time ); public: /** * Create a temporal filter for tone-curves. This shoudl be used to * tonemap video sequences. * * @param fps - frames per second. Only certain values are allowed * as all filters are precomputed at the moment. See the code for * more details. * @param y_min - minimum log10 display luminance (for clamping the * results) * @param y_max - maximum log10 display luminance (for clamping the * results) */ datmoTCFilter( float fps, float y_min, float y_max ); /** * Get the pointer to store the tone-curve. */ datmoToneCurve *getToneCurvePtr(); /** * Get the filterted tone-curve. */ datmoToneCurve *filterToneCurve(); }; class datmoConditionalDensity { public: virtual ~datmoConditionalDensity(); }; typedef int datmoVisualModel; #define vm_none 0 #define vm_luminance_masking 1 #define vm_contrast_masking 2 #define vm_csf 4 #define vm_full 7 /** * Tone-map RGB radiance map using the display adaptive tone * mapping. This is a convienience function that calls three stages of * this tone-mapping algorithm: datmo_compute_conditional_density(), * datmo_compute_conditional_density(), and * datmo_apply_tone_curve(). If you need a finer control over the * algorithm execution or want to tone-map video, use the lower levels * functions instead. * * @param R_out output red-channel (in pixel values). Can be the same * as R_in. * @param G_out output green-channel (in pixel values). Can be the same * as G_in. * @param B_out output blue-channel (in pixel values). Can be the same * as B_in. * @param width image width in pixels * @param height image height in pixels * @param R_in red input radiance map. * @param G_in green input radiance map. * @param B_in blue input radiance map. * @param L_in input luminance map (L=0.212656*R + 0.715158*G + 0.072186*B) * @param df display function. See DisplayFunction class documentation for more details. * @param ds display size. See DisplaySize class documentation for more details. * @param enh_factor conrast enhancement factor. See man * pfstmo_mantiuk08 page for details * @param saturation_factor color saturation factor. See man * pfstmo_mantiuk08 page for details * @param white_y luminance factor in the input image that should be * mapped to the maximum luminance of a display. If the parameter is * set to -1, the tone-mapper will not anchor to white (recommended for HDR images). * @param progress_cb callback function for reporting progress or stopping computations. * @return PFSTMO_OK if tone-mapping was sucessful, PFSTMO_ABORTED if * it was stopped from a callback function and PFSTMO_ERROR if an * error was encountered. */ int datmo_tonemap( float *R_out, float *G_out, float *B_out, int width, int height, const float *R_in, const float *G_in, const float *B_in, const float *L_in, DisplayFunction *df, DisplaySize *ds, const float enh_factor = 1.f, const float saturation_factor = 0.4f, const float white_y = -1, pfstmo_progress_callback progress_cb = NULL ); /** * Computes image statistics required for * datmo_compute_tone_curve(). This is the most time-consuming * function. If interactive tuning of the TMO parameters is needed, * this function can be executed once per image and then * datmo_compute_tone_curve() can be executed as many times as needed. * * @param width image width in pixels * @param height image height in pixels * @param L input luminance map (L=0.212656*R + 0.715158*G + 0.072186*B) * @param progress_cb callback function for reporting progress or stopping computations. * @return pointer to conditional_density or NULL if computation was * aborted or an error was encountered. The conditional_density object * must be freed by the calling application using the 'delete' * statement. */ std::auto_ptr datmo_compute_conditional_density( int width, int height, const float *L, pfstmo_progress_callback progress_cb = NULL ); /** * Computes the best tone-curve for a given conditional_density and * TMO parameters. The conditional_density must be computed with * datmo_compute_conditional_density() and freed afted executing this * function. * * @param tc datmoToneCurve where the resulting tone-curve will be stored * @param conditional_density image statistics computed with datmo_compute_conditional_density() * @param df display function. See DisplayFunction class documentation for more details. * @param ds display size. See DisplaySize class documentation for more details. * @param enh_factor conrast enhancement factor. See man * pfstmo_mantiuk08 page for details * @param white_y luminance factor in the input image that should be * mapped to the maximum luminance of a display. If the parameter is * set to -1, the tone-mapper will not anchor to white (recommended for HDR images). * @param progress_cb callback function for reporting progress or stopping computations. * @return PFSTMO_OK if tone-mapping was sucessful, PFSTMO_ABORTED if * it was stopped from a callback function and PFSTMO_ERROR if an * error was encountered. */ int datmo_compute_tone_curve( datmoToneCurve *tc, datmoConditionalDensity *cond_dens, DisplayFunction *df, DisplaySize *ds, const float enh_factor = 1.f, const float white_y = -1, pfstmo_progress_callback progress_cb = NULL, datmoVisualModel visualModel = vm_full, double scene_l_adapt = 1000 ); /** * Deprectaied: use datmo_apply_tone_curve_cc() * * Tone-map image using the tone-curve computed with datmo_compute_tone_curve(). * * @param R_out output red-channel (in pixel values). Can be the same * as R_in. * @param G_out output green-channel (in pixel values). Can be the same * as G_in. * @param B_out output blue-channel (in pixel values). Can be the same * as B_in. * @param width image width in pixels * @param height image height in pixels * @param R_in red input radiance map. * @param G_in green input radiance map. * @param B_in blue input radiance map. * @param L_in input luminance map (L=0.212656*R + 0.715158*G + 0.072186*B) * @param tc tone-curve computed with datmo_compute_tone_curve() * @param saturation_factor color saturation factor. See man * pfstmo_mantiuk08 page for details * @return PFSTMO_OK if tone-mapping was sucessful, PFSTMO_ABORTED if * it was stopped from a callback function and PFSTMO_ERROR if an * error was encountered. */ int datmo_apply_tone_curve( float *R_out, float *G_out, float *B_out, int width, int height, const float *R_in, const float *G_in, const float *B_in, const float *L_in, datmoToneCurve *tc, DisplayFunction *df, const float saturation_factor = 0.4f ); /** * Tone-map image using the tone-curve computed with * datmo_compute_tone_curve(). This function corrects color saturation * using the method from: * * Color Correction for Tone Mapping * Radosław Mantiuk, Rafał Mantiuk, Anna Tomaszewska, Wolfgang Heidrich. * In: Computer Graphics Forum (Proc. of EUROGRAPHICS'09), 28(2), 2009 * * @param R_out output red-channel (in pixel values). Can be the same * as R_in. * @param G_out output green-channel (in pixel values). Can be the same * as G_in. * @param B_out output blue-channel (in pixel values). Can be the same * as B_in. * @param width image width in pixels * @param height image height in pixels * @param R_in red input radiance map. * @param G_in green input radiance map. * @param B_in blue input radiance map. * @param L_in input luminance map (L=0.212656*R + 0.715158*G + 0.072186*B) * @param tc tone-curve computed with datmo_compute_tone_curve() * @param saturation_factor color saturation factor. Set to 1 to preserve colors, >1 to increase color saturation, <1 to reduce color saturation. * @return PFSTMO_OK if tone-mapping was sucessful, PFSTMO_ABORTED if * it was stopped from a callback function and PFSTMO_ERROR if an * error was encountered. */ int datmo_apply_tone_curve_cc( float *R_out, float *G_out, float *B_out, int width, int height, const float *R_in, const float *G_in, const float *B_in, const float *L_in, datmoToneCurve *tc, DisplayFunction *df, const float saturation_factor ); /** * Filter tone curves over time to avoid flickering. This filtering is * designed for 25 frames per second. * * @param in_tc array of input tone curves * @param count_in_tc the number of input tone curves in the array * @param out_tc the output tone curve. Must be pre-allocated. */ void datmo_filter_tone_curves( datmoToneCurve **in_tc, size_t count_in_tc, datmoToneCurve *out_tc ); pfstools-2.2.0/COPYING0000664000701400070140000006347614105165616013136 0ustar rkm38rkm38 GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. , 1 April 1990 Ty Coon, President of Vice That's all there is to it! pfstools-2.2.0/README.matlab0000664000701400070140000001067514105165635014214 0ustar rkm38rkm38 Matlab interface to pfstools can be found in src/matlab. It contains both .m and mex functions, which need to be compiled before can be used. Once pfstools is properly installed, you can browse help: doc pfstools_matlab or cd doc ./Contents.m If you have problems running some functions, you can execute (from matlab): pfs_test_shell to diagnose for common problems. Follow the instructions below to install matlab interface to pfstools. Linux, OSX and cygwin ===================== cmake will search for matlab's mex scripts in typical locations. If it cannot be found, you need to pass the matlab directory to cmake: cmake -DMATLAB_ROOT= ../ Matlab's MEX compiler commonly uses different version of gcc than is installed on the system. If this is the case, you may see messages, such as: Warning: You are using gcc version '5.4.0'. The version of gcc is not supported. The version currently supported with MEX is '4.7.x'. For a list of currently supported compilers see: http://www.mathworks.com/support/compilers/current_release. Sometimes those warning can be ignored, other times mex commands will fail with "Invalid MEX-file". In the latter case, you need to install the correct version of the compiler: sudo apt-get install g++-4.7 and then specify it when invoking cmake, for example: cmake -DMEXGCC=/usr/bin/gcc-4.7 ../ After successfully compiling the code, add the directory /usr/local/share/pfstools/pfstools_matlab to matlab path (File->Set Path). For a quick test, type in Matlab command window: pfsview( rand(100) ) so view a matrix of random numbers. Windows installation ==================== Note that matlab support on Windows has not been tested in 2.0.0. The notes below refer to 1.9.x version. Under Windows you have to invoke NMAKE file manually. From ordinary DOS shell (not cygwin), cd to src/matlab, then execute: (path to your matlab instalation)/matlab/R2006b/bin/mex.bat -setup mex.bat will ask to choose a configuration. Choose the one compatible with your Visual Studio installation. then execute: NMAKE -f Makefile.win32 We tested compilation on Win32 with VS C++ compiler. cygwin and pfstools must be installed. Then include this directory in matlab's path (File/SetPath in maltab IDE menu). You may need to modify pfs_shell() function that should return the command line for executing 'bash' from DOS shell. Many matlab pfs_* functions need to execute shell functions, for example pfsin. To make sure that they can be executed from matlab, all environmental variables must be set. Currently this is done by the pfs_shell function, which extends command line so that pfs* commands are executed from bash (assuming that bash sets all necessary environmental variables in .bashrc). If bash is not your default shell, you may need to change this. If no good-luck, then below is a loosely written trouble shooting: 1. From matlab, execute 'pfs_test_shell'. The function will perform a few tests for the most common pfstools/matlab setup problems and will suggest the most likely solution. 2. You can select a compiler for mex files using "mex -setup" If you have more than one compiler, for instance you owe a Visual Studio, then try various compilers. Sometimes things work with VC 8.0, but not with VC 6.0, and sometimes the other way around. 3. If you want to compile with Visual Studio, use the "Visual Studio 200X Command Prompt" DOS shell. Especially if "nmake" doesn't seem to be on your system. 4. All supplied Mex files do not depend on any library. So if you have a problem within Matlab which looks like "The specified module could not be found." it's not related to shared libraries. But it maybe related to incompatible Visual Studio libraries, see point 1. 5. If you got everything running and type "pfsview(my_image)" and nothing shows up: Check if X-Win32 or other X server is running, type "pfs_shell" in Matlab and check if path is correct (and possibly adjust the path in pfs_shell.m). Add a line to $HOME/.bash_login containing "export DISPLAY=127.0.0.1:0" 6. Under Windows, if your Cygwin installation is not in c:\cygwin set Windows global variable: CYGWIN_HOME='c:\MyDirectory\cygwin' Of course, c:\MyDirectory should be replaced with a right path. Known problems: * Under Windows shell window flashes each time pfsput / pfsget command is executed * pfs channel tags are not written to pfs-streams. No channel specific tags are written properly. * Some file handles may not be closed properly. pfstools-2.2.0/ubuntu-packages-dep0000664000701400070140000000025514105165635015655 0ustar rkm38rkm38cmake libopenexr-dev libmagick++-dev libnetpbm10-dev libtiff5-dev qt5-default freeglut3-dev libfftw3-dev libgsl-dev dcraw libraw-bin liboctave-dev libexif-dev libopencv-dev pfstools-2.2.0/AUTHORS0000664000701400070140000000113214105165635013131 0ustar rkm38rkm38The project is currently maintained by: Rafal Mantiuk Multiple people contributed to the project, including (in arbitrary order): Grzegorz Krawczyk Miloslaw Smyk Ivo Ihrke Alexander Efremov Dorota Zdrojewska Jan Otop Christian Bloch (Improved templates for HDR-HTML in src/hdthtml/hdrhtml_hdrlabs_templ) Radoslaw Mantiuk Oliver Barth Delvin Varghese Hugo Brito Lima George Ash Param Hanji pfstools-2.2.0/ChangeLog0000775000701400070140000004125614105165635013651 0ustar rkm38rkm38pfstools 2.2.0 <12.08.2021> * Added: v210 format in pfsinyuv * Fixed: memleak in pfssize * Fixed: Fix format-security errors with Octave 5.1 (thanks to Orion Poplawski) * Added: pfs_automerge * Fixed: typos in the documentation (thanks to Andreas Metzler) * Added: simple deghosting in pfshdrcalibrate (-d option) * Fixed: pfsalign now uses free AKAZE feature detector, avoiding issues with missing non-free OpenCV classes * Fixed: Updated installation instruction for Ubuntu 18.04 * Fixed: Fixed a number of compiler warnings from g++ 7.4.0 * Fixed: Octave interface upgraded to Octave-6 (thanks to Michal) * Fixed: pfstmo_durand02 - reversed the old fix that made images over-saturated. Now 99.5th percentile is mapped to white. * Removed: pfsinjpeghdr, pfsoutjpeghdr (discontinued) * Fixed: compilation fails with GCC11/std=c++17, duplicate "clamp" definition (thanks to Stefan) * Added: pfshdrcalibrate now performs exposure merging in a noise-optimal manner * Added: pfsindcraw now calls libraw's dcraw_emu instead of dcraw if the former is available pfstools 2.1.0 <13.11.2017> * Added: pfsinyuv/pfsoutyuv to handle uncompressed video .yuy * Added: Support for PQ2020 and HLG2020 color spaces (in pfs*yuv) * Added: pfsview now builds with Qt5 instead of Qt4 * Fixed: pfsglview crashing on OSX (thanks to Michael) * Fixed: issues with compiling matlab MEX functions on Ubuntu * Added: installation instruction for Ubuntu (README.Ubuntu) * Added: experimemtal version of deghosting in pfshdrcalibrate (poor performance) pfstools 2.0.6 <15.03.2017> * Fixed: --frame frame range spec can now handle negative frame order, e.g. 30:-1:0 * Added: Added photon-noise weighting in pfshdrcalibrate - very small improvement for some images * Fixed: pfsview does not change zoom settings when flipping between next/previous images * Fixed: Fixed bug in pfsin/outimgmagics - missing call to InitializeMagick causing assertion fault * Fixed: Fixed bug in pfsouthdrhtml - missing call to InitializeMagick causing assertion fault * Added: Matlab MEX files can now be compiled on Windows from Cygwin using VisualStudio compiler pfstools 2.0.5 <26.05.2016> * fixed: pfsinppm now can read multiple frames using --frames but also from a ppm file * fixed: a few typos in the documentation and code (thanks to Andreas) * fixed: fixed octave interface to work with Octave 3.8.1 (and hopefully later versions) * fixed: octave-config used to automatically determine were to install Octave files * fixed: compilation issues with c++-11 * fixed: bug in the assert statement made some matlab mex functions to crash (in debug mode) * fixed: version number string is now correctly reported * fixed: reverts mistakenly introduced fix from 2.0.0: pfsinpfm and pfsoutpfm do not flip images (top-bottom) * fixed: pfs handles now very long tags (by truncating them) pfstools 2.0.4 <15.07.2015> * fixed: added installation of octave-based scripts: pfsoctavelum pfsoctavergb pfsstat * fixed: libraries installed in lib64 if needed (thanks to Orion for the patch) * fixed: added "so" version to the pfs.so library (thanks to Orion for the patch) * fixed: Replaced depreciated OctaveMap for compatibility with octave 4.0.0 pfstools 2.0.3 <28.04.2015> * fixed: permission issues in the cmake scripts * fixed: Out-of-source compilation issues in octave (thanks to Tomas) * added: pfsoutexr has new --float32 option to store color as 32-bit float * upated: pfsoutexr assumes --fix-halfmax as default and has additional switch --clamp-halfmax to disable this behavior * fixed: pfshdrcalibrate: serious bug that caused Robertson's method to fail most of the time * fixed: pfshdrcalibrate: Improved clipping of saturated pixels should avoid most of the artefacts * added: jpeg2hdrgen - now the command can use "identify" from ImageMagick instead of "jhead" pfstools 2.0.2 <08.03.2015> * fixed: Fixed build and install scripts for hdrhtml * fixed: Fixed building FFT solver for pfstmo_fattal02 * updated: Added check for libexif * updated: improvements in pfstmo_ferradans11 to produce brighter results * updated: Updated some manual pages pfstools 2.0.1 <07.01.2015> * added: new tone-mapping operator "pfstmo_ferradans11" (thanks to Sira) pfstools 2.0.0 <06.12.2014> * added: Starting from pfstools 2.0.0, pfstmo and pfscalibration are included in the pfstools source package * changed: support for automake dropped in favour of cmake * fixed: pfsinpfm and pfsoutpfm now handle both big and little endian files correctly * fixed: pfsinpfm and pfsoutpfm do not flip images (top-bottom) * added: new tone-mapping operator: pfstmo_mai11 * changed: Thoroughly updated README files * fixed: several issues with octave scripts (compatibility with newer Octave releases), in particular pfs_write_rgb pfstools 1.9.0 <17.09.2014> * added: CMake build system (to replace automake in future releases) * changed: pfsinrgbe & pfsoutrgbe do not correct for Radiance WHITE_EFFICACY by default. Note that this makes absolute values stored in .hdr files using earlier pfstools version smaller then they should be. Check manual pages for pfsinrgbe. * added: pfsretime - simple frame duplication * added: pfscolortransform - color calibration * updated: pfsglview contains many improvement and multithreading (thanks to Oliver Barth) * updated: pfsview is multithreaded if openmp present * fixed: pfsindcraw uses sRGB rather than the native RGB color space. This should improve colors in HDR images merged from RAW. * fixed: bugs in pfsintiff * fixed: Building OSX app bundle for pfsview disabled by default (use --enable-osxapp to enable again) pfstools 1.8.5 <21.10.2011> * fixed: pfsintiff normalizes 16-bit files to 0-1 range to maintain compatibility with pfscalibration and the rest of pfstools * fixed: when reading TIFF files, pfsin falls back to pfsinimgmagick if pfsintiff not found * fixed: pfsout tries first pfsoutimgmagic when writing TIFF images pfstools 1.8.4 <20.05.2011> * fixed: SegFault from pfsview when switching channels * fixed: quoted arguments in pfsin/pfsout/pfsv/pfsindcraw/pfsinmulti (fixes bug 3279342) pfstools 1.8.3 <23.03.2011> * fixed: pfsdisplayfunction properly interpolates display LUT * fixed: bug #3080304 - configure uses non-portable test(1) syntax * fixed: bug #1766263 - pfsinhdrgen fails silently on pfsin failure. - partially * fixed: pfsview the entire code converted from qt3 to qt4 (fixes bug #3127946) * fixed: "Fit window to content" works again in pfsview * added: window icon in pfsview * added: improved image file saving in pfsview pfstools 1.8.2 <18.06.2010> * fixed: compiler incompatibility issue in pfspanoramic.cpp * fixed: bug #2953028 "gcc 4.4.1 const cast error" * fixed: spaces were improperly escaped in pfsin / pfsout (thanks to Timo) * added: new improved hdrhtml template from hdrlabs.com (thanks to Christian) pfstools 1.8.1 <01.06.2009> * fixed: hdrhtml JavaScript code for XHTML pages * fixed: hdrhtml '+' and '-' keys work now the same as in pfsview (were swapped) * fixed: added missing includes * fixed: matlab/pfs_shell.m removes matlab paths from LD_LIBRARY_PATH * fixed: uninitialized color clipping mode in pfsview (tracker 2790026) * fixed: pfsview, "preserve bri & hue" color clipping handles lower limit correcly now * fixed: pfsinppm does not report EOF error when reading from stdin * fixed: compilation on Mac OS X 10.5.7 with Fink * added: make install creates application bundle for pfsview pfstools 1.8 <23.02.2009> * added: pfsouthdrhtml for generating web pages with an HDR viewer * added: matlab functions: pfs_write_image, pfs_read_image * added: matlab interface can now save multi-channel (>3) images * fixed: cleaned up and improved matlab documentation, added Content.m * fixed: problem with inheriting matlab's LD_LIBRARY_PATH when running pfs commands * fixed: improved search for NETPBM header files in the configure script * fixed: pfssize keeps aspect ratio when resizing many images with --min/max/x/y pfstools 1.7.0 <22.10.2008> * added: pfsingdal - reader of geospatial data formats using GDAL (Geospatial Data Abstraction Library) (thanks to Martin Lambers) * added: pfsdisplayfunction command for conversion between display luminance and pixel values * added: pfsinimgmagick and pfsinppm add a WHITE_Y tag to frames (needed by some tone mapping operators) * pfsview: automatically switch to linear mapping and LDR range for display-referred images * fixed: pfsview correctly shows pixel position x=0 * added: inverse gamma correction in pfsgamma * added: pfsgamma sets propertly LUMINANCE tag and displays warnings on improper input images * added: configure script displays information and commands that will not be compiled because of missing dependencies * fixed: some warning messages due to stricter syntax in g++ 4.2.1 * bugfix: removed depreciated matlab command pfsread - caused compilation problems under Windows * added: pfs_test_shell for quick testing for common matlab setup problems * added: pfsview shows in the status bar current exposure for the dynamic range window (relative to Y=1) * added: configure should automatically find the include dir for netpbm * fixed: handling of >8bit files is unified for all commands; new tag ('BITDEPTH') is used to store information about the bit-depth recission; pfsoutimagemagic and pfsoutppm support a new option --bit-depth * fixed: removed --linear option from pfsoutppm, pfsoutimagemagick, pfsouttiff and changed default behavior, which was too confusing and error-prone. Images will never be gamma-corrected (transformed to the sRGB) unless it is explicitly enforced with a new option '--srgb'. pfstools 1.6.5 <06.05.2008> * fixed: matlab interface cleanup: pfsfclose.cpp pfsfgets.cpp pfsfputs.cpp pfspopen2.cpp pfssend.cpp moved to another project as they do not belong to pfstools * fixed: matlab Makefile automatically detects correct mex-file extension (thanks to Neil Alldrin) * fixed: matlab - pfs_shell under unix adds bash as the default shell for executing pfs commands * fixed: matlab interface operates now on single precission floats, thus making most operations faster (thanks to Neil Alldrin) * added: matlab -> pfsview function shows now matrix names * Debian patch ported: changes required to switch to octave3.0 (thanks to Thomas Weber) * Debian patch ported: Fixed the usage of dcraw(1)'s -m command line option (thanks to Sebastian Harl) pfstools 1.6.4 <01.01.2007> * fixed: buffer overflow vulnerability in rgbeio.cpp (thanks to Stefan) * fixed: compilation issues with gcc-4.3 - missing includes (thanks to Sebastian Harl for the Debian patch) pfstools 1.6.3 <05.12.2007> * fixed: pfsinopenexr can now read files that has data window < display window * fixed: pfs library can handle channel names up to 32 characters (was 8) * updated: pfs specification - max string lengths and format of custom channel names * security fix: fscanf in rgbeio.cpp (thanks to Stefan and Ludwig) * bugfix: pfsview - segfault when switching channels * fixed: pfsview - color readout in the status line refreshed when new channel or frame loaded * added: matlab/pfs_write_luminance.m * bugfix: rgbeio - header read properly when the first byte is 0x20 (thanks to Axel) * bugfix: rgbeio - fix misinterpreted rle-compresses lines (thanks to Axel) pfstools 1.6.2 <04.07.2007> * matlab: pfsview can now display 2D cell arrays * pfs library: quite serious bug in sRGB transforms fixed * added: check for GLUT library (unix only) * added: man page for pfsglview pfstools 1.6.1 <24.04.2007> * added: pfsin accepts all extensions supported by dcraw (thanks to L. David Baron) * fixed: tiff logluv reader - segfault bug and wrong colorspace conversions (thanks to Giuseppe Rota) * updated: some documentation files * fixed: matlab/pfs_transform_colorspace accepts 3D/2D matrix as both input and output * fixed: matlab/pfs_put or _get handles tags in pfs stream * fixed: pfsview under different shell than bash pfstools 1.6 <01.03.2007> * added: preliminary support for matlab (see README.matlab) * added: --disable-octave option * fixed: configure.ac honors CXXFLAGS instead of overwriting them with -O3 * added: pfsin/outimgmagic now handles alpha channel * added: pfsindcraw checks for dcraw and reports errors if not found * fixed: pfsintiff could fail with large images * added: example project files for MS VC++ (CVS only) * added: pfsglview (viewer, which does not require qt) * added: support for exposure adjustment in radiance HDR files * fixed: compiler compatibity issues in pfspanoramic.cpp pfstools 1.5 <16.08.2006> * All octave IO API updated: named pipes replaced with popen, which is less problematic under cygwin * bugfix: color conversion matrixes (XYZ->RGB) lead to inaccuracies for 16bit images. matrix values have been adjusted to maximize precision. (2006-08-08) * added: initial support for alpha channels in Tiff files (thanks to Pablo d'Angelo, http://hugin.sf.net). images can be loaded, but alpha channels are ignored. * added: disable-* options to configure script to disable build of certain features * added: pfsview - displayed image can be copied to clipboard * added: pfscut - now it is possible to specify u-l, b-r coordinates * updated: pfs specification pfstools 1.4 * added: initial support for camera RAW files via dcraw wrapper pfsindcraw 2006-03-17 Grzegorz Krawczyk * pfsview: now possible to choose color space for the pixel under the cursor * pfsview: added new color clipping method: Keep brightness and hue * improved QT autoconf check, added --with-qtinclude, --with-qtlibs * fixed typos in documentation and pfs spec (thanks to Adam Buchbinder) * pfs.cpp and colorspace.cpp compile under MS Visual C++ * pfsopen.m can accept file descriptor of an opened file now pfstools 1.3 * added: pfsinimgmagick/pfsoutimgmagick to read and write images using ImageMagick++ library * added: pfsinjpeghdr / pfsoutjpeghdr for JPEG-HDR format * added: pfsstat - hdr image statistic * bug fixed: loading of images in pfsview should be 30-60% faster * configure.ac - improved QT config script ** use pkgconfig to set up QT flags, if possible ** verify MOC ** simpler script to set up QT flags * libpfs uses map instead of hash_map - for better compatibility * added: pfsview - better handling of negative values ** mark negative with red now works ** negative values in color images are now recognized * pv renamed to pfsv to avoid the conflict with the pipe viewer 'pv' pfstools 1.2.1 <19.07.2005> * bug fixed: seg fault in pfsoutexr and pfsabsolute * bug fixed: pfsin uses bash as a shell pfstools 1.2 <15.06.2005> pfs library: extended api: * pckconfig file renamed from pfs-1.0.pc to pfs.pc * iterator over all tags in TagContainer * new iterator over all channels in Frame * FrameFileIterator can be used together with getopt and getopt_long pfsinexr, pfsoutexr: can read/write all channels and all tags/attributes added: pfsinpfm, pfsoutpfm - to read/write PFM HDR images pfsin*,pfsout*: now use 'LUMINANCE' tag to keep the track of the type of intensities stored in the pfs stream: relative (linear) or display (gamma corrected) pfssize: new arguments --minx, --miny, --maxx and --maxy pfs library: sRGB color space scaled to keep channel values within 0-1 range (to be compliant with the other parts of pfstools) Added new filters * pfscut - crops images * pfscat - concatenates images or animations * pfspad - add border to images or animations * pfsrotate - rotates images by 90deg * pfsflip - flips images * pfsabsolute - calibrates images to absolute units (Y in cd/m^2) Support for LUMINANCE tag (recognized by some pfsin* and pfsout* commands) pfstools 1.1 <29.01.2005> pfsview: minimum and maximum of the luminance window can be dragged (previously only entire window could be dragged) 10.01.2004 added: support for gcc 3.4.2 (thanks to Fabio Mierlo) 15.12.2004 added: pfspanoramic 05.12.2004 pfsview: optimized mapping function for faster display and better responsiveness pfsview: added visualization of negative values pfsview: added option to switch off NaN and Inf marking pfssize: fixed problem with boundary conditions pfsview: shortcuts added 24.11.2004 added: octave script: pfssize transformColorSpace does graph traverse to find conversion between any color spaces 18.11.2004 pfsview: can save visible window as .png image 17.11.2004 pfsextractchannels: added Updated man pages 02.11.2004 pfsview: it is now possible to move back and forth between the frames pfsin*: all those commands now add a tag FILE_NAME 27.10.2004 Rewritten and much improved pfssize. Enlarging of images is now possible. pfssize rewrites tags to the resized stream. pfstools-2.2.0/README.Cygwin0000664000701400070140000000443614105165635014212 0ustar rkm38rkm38pfstools can be compiled under Windows from the cygwin environment. You can download cygwin from: http://www.cygwin.com/ ==================================================== How to compile and install? ==================================================== This instruction was tested in August of 2021 with cygwin 3.2.0. 1. Install 64-bit version of cygwin from https://cygwin.com/index.html 2. Select the following cygwin packages: git gcc-g++ make cmake perl zlib-devel libilmbase-devel libilmimf-devel libnetpbm-devel libMagick-devel (IMPORTANT: must be version 6. pfstools is incompatible with version 7.) libtiff-devel libgsl-devel libexif-devel libfftw3-devel libopencv-devel libraw Install the latest non-test release of each package. The list does not include Qt and OpenGL packages so that pfsview will not be compiled. As pfsview is rather awkward to use using an X window client, it is recommended to use a native win32 version instead, as explained in Step 7. 3. Open cygwin terminal and clone the latest version of pfstools: git clone https://git.code.sf.net/p/pfstools/git pfstools Alternatively, you can compile from a prepackaged ZIP file, though the version may not be a bit outdated. 4. Using the cygwin terminal, create "build" directory and run cmake: cd pfstools mkdir build cd build cmake ../ Ignore warnings about missing OpenGL, Qt and Matlab compiler. Do not set MATLAB_MEX as the precompiled files will be installed in step 6. 5. Make and install: make install 6. Matlab interface comes precompiled in the "pfstools_matlab" directory. Add this directory to the matlab path. 7. For pfsview image viewer, unzip the win32 version of pfstools, which can be downloaded from: https://sourceforge.net/projects/pfstools/files/pfstools_visual_studio_incomplete/ into "C:\Program Files (x86)\pfstools". Then, create a symbolic link (from Cygwin terminal): ln -s "/cygdrive/c/Program Files (x86)/pfstools/bin/pfsview.exe" /usr/local/bin/pfsview Repeat step 4 to install pfsv after the precompiled pfsview has been installed. 7. Test that everything works. - In cygwin: pfsin | pfsview - In matlab: pfs_test_shell I = pfs_read_image( '.hdr' ); pfsview( I ); You can download some HDR images from: https://sourceforge.net/projects/pfstools/files/hdr_images/ pfstools-2.2.0/README.Ubuntu0000664000701400070140000000074714105165635014235 0ustar rkm38rkm38 pfstools is in the first order supported on Ubuntu as this is the OS we use on everyday basis. The procedure below has been tested on Ubuntu 18.04 LTS and Ubuntu 20.04 LTS To compile pfstools on Ubuntu follow the steps: 1. Execute: sudo apt-get install `cat ubuntu-packages-dep | tr '\n' ' '` 2. If you want to install pfstools with Matlab support, follow the steps in README.Matlab 3. From the pfstools directory, execute: mkdir build cd build cmake ../ make sudo make install pfstools-2.2.0/README0000664000701400070140000002064614105165635012754 0ustar rkm38rkm38pfstools 2.2.0 <12.08.2021> This is mostly a bug-fix release with many fixes allowing to work with newer version of libraries. Several compilation issues have been resolved on Ubuntu 18.04, 20.04 and Cygwin. The HDR merging (pfshdrcalibrate) uses now better, noise-optimal weights, which should reduce noise for darker image parts. * Added: v210 format in pfsinyuv * Fixed: memleak in pfssize * Fixed: Fix format-security errors with Octave 5.1 (thanks to Orion Poplawski) * Added: pfs_automerge * Fixed: typos in the documentation (thanks to Andreas Metzler) * Added: simple deghosting in pfshdrcalibrate (-d option) * Fixed: pfsalign now uses free AKAZE feature detector, avoiding issues with missing non-free OpenCV classes * Fixed: Updated installation instruction for Ubuntu 18.04 * Fixed: Fixed a number of compiler warnings from g++ 7.4.0 * Fixed: Octave interface upgraded to Octave-6 (thanks to Michal) * Fixed: pfstmo_durand02 - reversed the old fix that made images over-saturated. Now 99.5th percentile is mapped to white. * Removed: pfsinjpeghdr, pfsoutjpeghdr (discontinued) * Fixed: compilation fails with GCC11/std=c++17, duplicate "clamp" definition (thanks to Stefan) * Added: pfshdrcalibrate now performs exposure merging in a noise-optimal manner * Added: pfsindcraw now calls libraw's dcraw_emu instead of dcraw if the former is available pfstools - README ------------------------------------------------------------------- For issues related to Ubuntu installation - see README.Ubuntu For issues related to Mac OSX installation - see README.OSX For issues related to Windows installation - see README.Cygwin For specific notes on Matlab installation - see README.matlab For compilation with Visual Studio (experimental) - see README.VisualStudio pfstools is a set of command line (and two GUI) programs for reading, writing, manipulating and viewing high-dynamic range (HDR) images and video frames. All programs in the package exchange data using unix pipes and a simple generic HDR image format (pfs). The concept of the pfstools is similar to netpbm package for low-dynamic range images. pfstools offers also a good integration with GNU Octave and matlab. pfstools can serve as a matlab or Octave toolbox for reading and writing HDR images. pfs in not just another format for storing HDR images. It is more an attempt to integrate the existing HDR image formats by providing a simple data format that can be used to exchange data between applications. If you use the software for your research work, please consider citing the paper: Rafal Mantiuk, Grzegorz Krawczyk, Radoslaw Mantiuk and Hans-Peter Seidel. High Dynamic Range Imaging Pipeline: Perception-motivated Representation of Visual Content. In: Proc. of Human Vision and Electronic Imaging XII. 649212. @inproceedings{mantiuk:2007:hvei, author = {Mantiuk, Rafa{\l} and Krawczyk, Grzegorz and Mantiuk, Rados{\l}aw and Seidel, Hans-Peter}, editor = {Rogowitz, Bernice E. and Pappas, Thrasyvoulos N. and Daly, Scott J.}, title = {High Dynamic Range Imaging Pipeline: Perception-motivated Representation of Visual Content}, booktitle = {Human Vision and Electronic Imaging XII}, publisher = {SPIE}, year = {2007}, volume = {6492}, number = {649212}, series = {Proceedings of SPIE}, address = {San Jose, USA}, month = {February}, } The paper is an introduction to both pfstools and HDR imaging in general. It can be downloaded from: http://pfstools.sourceforge.net/papers/mantiuk07hdr_pipeline.pdf 1. Compilation ------------------------------------------------------------------- Is is strongly recommended to compile into an out-of-source directory: cd mkdir build cd build cmake ../ make If the compilation fails because of a missing library, you can disable the offending component by setting WITH_ to false. For example: cmake -DWITH_MATLAB=false ../ to disable matlab support. If you encounter any problems during compilation, run the make again with the option: make VERBOSE=1 or, if you use nmake on Windows: nmake /S and report the problem with the complete error message to the google discussion group: https://groups.google.com/forum/#!forum/pfstools 2. Directory Layout ------------------------------------------------------------------- doc - documentation src - all sources go there pfs - pfs library (used by all tools) fileformat - readers and writters for various file formats filter - a range of "filters" from resize to changing color space octave - GNU Octave scripts and libraries matlab - matlab mex sources and functions pfsview - qt application for viewing hdr images and other data that can be stored in the pfs stream pfsglview - similar as pfsview, but uses OpenGL & GLUT instead of Qt tmo - contains tone-mapping operators (formerly pfstmo package) camera - contains HDR merging and camera calibration tools (formerly pfscalibration) hdrhtml - makes HTML5 web-pages with viewable HDR images getopt - used on Windows to compile using Visual Studio debian - scripts to create Debian distro package. The scripts has not been updated and currently is not working. 3. Dependencies ------------------------------------------------------------------- pfstools relies on a large number of libraries and other dependencies. However, the build script was designed to succeed even if some dependencies are missing. In that case, the tools that rely on those dependencies will not be compiled. If building with a certain dependency causes problems, it is possible to manually switch off that component by passing -DWITH_??=false to cmake. Note that the development packages must be installed for all dependencies. They usually have '-dev' suffix. Below is the list of all dependencies requires for pfstools. They are more or less given on an order of importance. OpenEXR CMake switch: WITH_OpenEXR Tools: pfsinexr, pfsoutexr ImageMagick CMake switch: WITH_ImageMagick Tools: pfsinimgmagick, pfsoutimgmagick Used to read/write most LDR files. Required for 16-bit file support. "pfsin"/"pfsout" will use NetPBM if ImageMagick is not available NetPBM CMake switch: WITH_NetPBM Tools: pfsinppm, pfsoutppm, (pfsindcraw) Alternative method of reading/writing LDR images. Note that this package is also required for pfsindcraw as ImageMagick cannot handle files read from unix pipes. TIFF CMake switch: WITH_TIFF Tools: pfsintiff, pfsouttiff TIFF images can also be read with pfsinimgmagick. This is a custom reader/writer, which supports LogLuv TIFF HDR image format. QT 5 CMake switch: WITH_QT Tools: pfsview "pfsview" is a primary HDR image viewer. If Qt library is not found, you need to specify the path to its cmake configuration files: cmake -D "CMAKE_PREFIX_PATH=~/Qt5location/qt5/5.7/gcc_64/" ../ OpenGL, GLUT (or FreeGLUT) CMake switch: WITH_pfsglview Tools: pfsglview pfsglview is a "backup" or alternative viewer. It is missing some features of pfsview, but offers faster zooming. Matlab (mex) CMake switch: WITH_MATLAB Tools: all matlab scripts in src/matlab Note that you need to set matlab path to /share/pfstools/matlab Octave CMake switch: WITH_Octave Tools: All Octave scripts in src/octave There are still some unresolved issues running Octave scripts under cygwin. FFTW CMake switch: WITH_FFTW Tools: acceleration in pfstmo_durand02 (the TMO will be *very* slow otherwise) GSL CMake switch: WITH_GSL Tools: pfstmo_mantiuk08 The GSL math library is required for this TMO. OpenCV CMake switch: WITH_OpenCV Tools: pfsalign Note that pfsalign requires SURF features, which are available on "nonfree" part of the library. Since part of the library is currently not included in most Linux distributions, so you will need to compile OpenCV from sources. libraw CMake switch: none Tools: pfsindcraw From 2.2.0 libraw is used instead of dcraw, if it is available. libraw has better support for newer cameras. libexif Used in pfsalign. 3. Documentation ------------------------------------------------------------------- First check the list of frequently asked questions in ./doc/faq.txt. Then browse relevant manual pages, which accompany each program. The documentation for the pfs library API can be generated with DoxyGen or found in the header files. If you want to include reading or writing of pfs streams in your applications, refer to ./doc/pfs_format_spec.pdf. pfstools-2.2.0/README.VisualStudio0000664000701400070140000000523414105165635015402 0ustar rkm38rkm38pfstools can be compiled with Visual Studio using CMake build system. This file contains some troubleshooting hints. Refer to README for details on building pfstools using CMake. The compilation was tested with Visual Studio Community 2019, 64-bit. As of 6/10/2019 only a portion of the pfstools programs can be compiled with MSVC. This is partly because the code uses a feautures or includes not available in MSVC. To compile selected programs, you will probably need to comment out in src/CMakeFiles.txt selected directories. To compile a native version of pfsview on Windows, check README.MinGW. Note that this compilation procedure is an alternative to using cygwin (refer to README.Cygwin). One exception is pfsview, which works better when natively compiled with Qt5 for Windows rather than with XWindow cleint in cygwin. === zlib === zlib (http://www.zlib.net/) is required to compile pfstools. You can download pre-compiled libraries from: https://www.bruot.org/hp/libraries/ then update CMakeLists.txt to include the relevant path. Find the commented lines: %SET(ZLIB_DIR "C:/") %SET(CMAKE_INCLUDE_PATH ${ZLIB_DIR}/include ${CMAKE_INCLUDE_PATH}) %SET(CMAKE_LIBRARY_PATH ${ZLIB_DIR}/ ${ZLIB_DIR}/lib ${CMAKE_LIBRARY_PATH}) uncomment them and update the path. === QT === Update from 6/10/2019: Qt (pfsview) compilation with VisualStudio is not tested. This is because Qt does not support Visual Studio C++ 2019, which is the only available download at the Microsoft web pages. Update from 27/12/2019: QT provides the libraries for VisualStudio 2019 now but compilation results in a long list of errors. Giving up. pfsview compiled with MinGW GCC works fine. Download and install Qt5 for Visual Studio from: http://qt-project.org/downloads Then add entry to CMake variables: -DQt5_DIR=C:/Qt/5.13.0/msvc2017_64/lib/cmake/Qt5 where C:/Qt/5.13.0/msvc2017_64/lib/cmake/Qt5 should be replaced with the path to qmake on your computer. Use "/" instead of "\" ??You may need to compile in Release mode. Otherwise some DLLs might be missing. === OpenEXR === Compiling OpenEXR on Windows with Visual Studio is not a task for faint hearted. Be prepared to fix quite a few issues with the code. * If the compiler complains about missing "__builtin_trap()", add #define PLATFORM_WINDOWS 1 to config/IlmBaseConfig.h see https://lists.nongnu.org/archive/html/openexr-devel/2013-12/msg00001.html * If the compiler complains about "std::max", add missing include in all offending files: #include Other useful web pages: http://buildopenexr.blogspot.co.uk/ http://blog.gmane.org/gmane.comp.video.openexr.devel