mirror of https://github.com/tp4a/teleport
Merge branch 'dev' of github.com:tp4a/teleport into dev
commit
cd6255178f
|
@ -1,8 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="PYTHON_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<content url="file://$MODULE_DIR$/builder" />
|
||||
<orderEntry type="jdk" jdkName="py37" jdkType="Python SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
<component name="TestRunnerService">
|
||||
|
|
|
@ -48,8 +48,9 @@ class BuilderBase:
|
|||
self._build_openssl(file_name)
|
||||
|
||||
def _build_openssl(self, file_name):
|
||||
_alt_ver = '_'.join(env.ver_openssl.split('.'))
|
||||
_alt_ver = '_'.join(env.ver_ossl.split('.'))
|
||||
if not utils.download_file('openssl source tarball', 'https://github.com/openssl/openssl/archive/OpenSSL_{}.zip'.format(_alt_ver), PATH_DOWNLOAD, file_name):
|
||||
cc.e("can not download openssl source tarball.")
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
@ -145,8 +146,9 @@ class BuilderWin(BuilderBase):
|
|||
def _build_openssl(self, file_name):
|
||||
cc.n('build openssl static library from source code... ')
|
||||
|
||||
_alt_ver = '_'.join(env.ver_ossl.split('.'))
|
||||
if not utils.download_file('openssl source tarball', 'https://github.com/openssl/openssl/archive/OpenSSL_{}.zip'.format(_alt_ver), PATH_DOWNLOAD, file_name):
|
||||
if not super()._build_openssl(file_name):
|
||||
# _alt_ver = '_'.join(env.ver_ossl.split('.'))
|
||||
# if not utils.download_file('openssl source tarball', 'https://github.com/openssl/openssl/archive/OpenSSL_{}.zip'.format(_alt_ver), PATH_DOWNLOAD, file_name):
|
||||
return
|
||||
|
||||
_chk_output = [
|
||||
|
@ -201,11 +203,7 @@ class BuilderWin(BuilderBase):
|
|||
out_file_lib = os.path.join(self.LIBSSH_PATH_SRC, 'lib', ctx.target_path, 'ssh.lib')
|
||||
out_file_dll = os.path.join(self.LIBSSH_PATH_SRC, 'lib', ctx.target_path, 'ssh.dll')
|
||||
|
||||
need_build = False
|
||||
if not (os.path.exists(out_file_lib) and (os.path.exists(out_file_dll))):
|
||||
need_build = True
|
||||
|
||||
if not need_build:
|
||||
if os.path.exists(out_file_lib) and os.path.exists(out_file_dll):
|
||||
cc.w('already exists, skip.')
|
||||
return
|
||||
cc.v('')
|
||||
|
@ -495,10 +493,6 @@ class BuilderLinux(BuilderBase):
|
|||
pass
|
||||
os.chdir(old_p)
|
||||
|
||||
# utils.ensure_file_exists(os.path.join(self.LIBSSH_PATH_SRC, 'build', 'src', 'libssh.a'))
|
||||
# utils.copy_file(os.path.join(self.LIBSSH_PATH_SRC, 'build', 'src'), os.path.join(self.PATH_RELEASE, 'lib'), 'libssh.a')
|
||||
# utils.copy_ex(os.path.join(self.LIBSSH_PATH_SRC, 'include'), os.path.join(self.PATH_RELEASE, 'include'), 'libssh')
|
||||
|
||||
utils.ensure_file_exists(os.path.join(self.PATH_RELEASE, 'lib', 'libssh.a'))
|
||||
files = os.listdir(os.path.join(self.PATH_RELEASE, 'lib'))
|
||||
for i in files:
|
||||
|
@ -506,23 +500,6 @@ class BuilderLinux(BuilderBase):
|
|||
# use os.unlink() because some file should be a link.
|
||||
os.unlink(os.path.join(self.PATH_RELEASE, 'lib', i))
|
||||
|
||||
# def _build_sqlite(self, file_name):
|
||||
# if not os.path.exists(self.SQLITE_PATH_SRC):
|
||||
# os.system('tar -zxvf "{}/{}" -C "{}"'.format(PATH_DOWNLOAD, file_name, self.PATH_TMP))
|
||||
#
|
||||
# cc.n('build sqlite static...', end='')
|
||||
# if os.path.exists(os.path.join(self.PATH_RELEASE, 'lib', 'libsqlite3.a')):
|
||||
# cc.w('already exists, skip.')
|
||||
# return
|
||||
# cc.v('')
|
||||
#
|
||||
# old_p = os.getcwd()
|
||||
# os.chdir(self.SQLITE_PATH_SRC)
|
||||
# os.system('./configure --prefix={}'.format(self.PATH_RELEASE))
|
||||
# os.system('make')
|
||||
# os.system('make install')
|
||||
# os.chdir(old_p)
|
||||
|
||||
def fix_output(self):
|
||||
pass
|
||||
# remove .so files, otherwise will link to .so but not .a in default.
|
||||
|
@ -541,7 +518,7 @@ class BuilderMacOS(BuilderBase):
|
|||
def _init_path(self):
|
||||
self.PATH_TMP = os.path.join(PATH_EXTERNAL, 'macos', 'tmp')
|
||||
self.PATH_RELEASE = os.path.join(PATH_EXTERNAL, 'macos', 'release')
|
||||
# self.OPENSSL_PATH_SRC = os.path.join(self.PATH_TMP, 'openssl-OpenSSL_{}'.format(env.ver_openssl.replace('.', '_')))
|
||||
self.OPENSSL_PATH_SRC = os.path.join(self.PATH_TMP, 'openssl-OpenSSL_{}'.format(env.ver_ossl.replace('.', '_')))
|
||||
self.LIBUV_PATH_SRC = os.path.join(self.PATH_TMP, 'libuv-{}'.format(env.ver_libuv))
|
||||
self.MBEDTLS_PATH_SRC = os.path.join(self.PATH_TMP, 'mbedtls-mbedtls-{}'.format(env.ver_mbedtls))
|
||||
self.LIBSSH_PATH_SRC = os.path.join(self.PATH_TMP, 'libssh-{}'.format(env.ver_libssh))
|
||||
|
@ -572,24 +549,35 @@ class BuilderMacOS(BuilderBase):
|
|||
cc.w('already exists, skip.')
|
||||
|
||||
def _build_openssl(self, file_name):
|
||||
pass # we do not need build openssl anymore, because first time run build.sh we built Python, it include openssl.
|
||||
#
|
||||
# if not os.path.exists(self.OPENSSL_PATH_SRC):
|
||||
# os.system('tar -zxvf "{}/{}" -C "{}"'.format(PATH_DOWNLOAD, file_name, self.PATH_TMP))
|
||||
#
|
||||
# cc.n('build openssl static...', end='')
|
||||
# if os.path.exists(os.path.join(self.PATH_RELEASE, 'lib', 'libssl.a')):
|
||||
# cc.w('already exists, skip.')
|
||||
# return
|
||||
#
|
||||
# old_p = os.getcwd()
|
||||
# os.chdir(self.OPENSSL_PATH_SRC)
|
||||
# # os.system('./config --prefix={} --openssldir={}/openssl no-zlib no-shared'.format(self.PATH_RELEASE, self.PATH_RELEASE))
|
||||
# # os.system('./Configure darwin64-x86_64-cc')
|
||||
# os.system('./Configure darwin64-x86_64-cc --prefix={} --openssldir={}/openssl -fPIC no-zlib no-shared'.format(self.PATH_RELEASE, self.PATH_RELEASE))
|
||||
# os.system('make')
|
||||
# os.system('make install')
|
||||
# os.chdir(old_p)
|
||||
if not super()._build_openssl(file_name):
|
||||
return
|
||||
|
||||
cc.n('prepare openssl source code...')
|
||||
_alt_ver = '_'.join(env.ver_ossl.split('.'))
|
||||
if not os.path.exists(self.OPENSSL_PATH_SRC):
|
||||
# utils.unzip(os.path.join(PATH_DOWNLOAD, file_name), PATH_EXTERNAL)
|
||||
os.system('unzip "{}/{}" -d "{}"'.format(PATH_DOWNLOAD, file_name, self.PATH_TMP))
|
||||
# os.rename(os.path.join(PATH_EXTERNAL, 'openssl-OpenSSL_{}'.format(_alt_ver)), self.OPENSSL_PATH_SRC)
|
||||
if not os.path.exists(self.OPENSSL_PATH_SRC):
|
||||
raise RuntimeError('can not prepare openssl source code.')
|
||||
else:
|
||||
cc.w('already exists, skip.')
|
||||
|
||||
cc.n('build openssl static...', end='')
|
||||
out_file_lib = os.path.join(self.PATH_RELEASE, 'lib', 'libssl.a')
|
||||
if os.path.exists(out_file_lib):
|
||||
cc.w('already exists, skip.')
|
||||
return
|
||||
cc.v('')
|
||||
|
||||
old_p = os.getcwd()
|
||||
os.chdir(self.OPENSSL_PATH_SRC)
|
||||
# os.system('./config --prefix={} --openssldir={}/openssl no-zlib no-shared'.format(self.PATH_RELEASE, self.PATH_RELEASE))
|
||||
# os.system('./Configure darwin64-x86_64-cc')
|
||||
os.system('./Configure darwin64-x86_64-cc --prefix={} --openssldir={}/openssl -fPIC no-zlib no-shared'.format(self.PATH_RELEASE, self.PATH_RELEASE))
|
||||
os.system('make')
|
||||
os.system('make install')
|
||||
os.chdir(old_p)
|
||||
|
||||
def _build_libuv(self, file_name):
|
||||
cc.n('prepare libuv source code...', end='')
|
||||
|
@ -673,51 +661,21 @@ class BuilderMacOS(BuilderBase):
|
|||
cc.v('')
|
||||
|
||||
build_path = os.path.join(self.LIBSSH_PATH_SRC, 'build')
|
||||
# utils.makedirs(build_path)
|
||||
|
||||
# here is a bug in cmake v2.8.11 (default on ubuntu14), in FindOpenSSL.cmake,
|
||||
# it parse opensslv.h, use regex like this:
|
||||
# REGEX "^#define[\t ]+OPENSSL_VERSION_NUMBER[\t ]+0x([0-9a-fA-F])+.*")
|
||||
# but in openssl-1.0.2h, the version define line is:
|
||||
# # define OPENSSL_VERSION_NUMBER 0x1000208fL
|
||||
# notice there is a space char between # and define, so find openssl always fail.
|
||||
|
||||
# old_p = os.getcwd()
|
||||
# os.chdir(build_path)
|
||||
# cmd = 'cmake' \
|
||||
# ' -DCMAKE_INSTALL_PREFIX={}' \
|
||||
# ' -D_OPENSSL_VERSION={}' \
|
||||
# ' -DOPENSSL_INCLUDE_DIR={}/include' \
|
||||
# ' -DOPENSSL_LIBRARIES={}/lib' \
|
||||
# ' -DCMAKE_BUILD_TYPE=Release' \
|
||||
# ' -DWITH_GSSAPI=OFF' \
|
||||
# ' -DWITH_ZLIB=OFF' \
|
||||
# ' -DWITH_STATIC_LIB=ON' \
|
||||
# ' -DWITH_PCAP=OFF' \
|
||||
# ' -DWITH_EXAMPLES=OFF' \
|
||||
# ' -DWITH_NACL=OFF' \
|
||||
# ' ..'.format(self.PATH_RELEASE, OPENSSL_VER, self.PATH_RELEASE, self.PATH_RELEASE)
|
||||
# cc.n(cmd)
|
||||
# os.system(cmd)
|
||||
# # os.system('make ssh_static ssh_threads_static')
|
||||
# os.system('make ssh_static')
|
||||
# # os.system('make install')
|
||||
# os.chdir(old_p)
|
||||
|
||||
cmake_define = ' -DCMAKE_INSTALL_PREFIX={prefix}' \
|
||||
' -D_OPENSSL_VERSION={oss_ver}' \
|
||||
' -DOPENSSL_INCLUDE_DIR={ossl_inc}' \
|
||||
' -DOPENSSL_LIBRARIES={ossl_lib}' \
|
||||
cmake_define = ' -DCMAKE_INSTALL_PREFIX={path_release}' \
|
||||
' -DOPENSSL_INCLUDE_DIR={path_release}/include' \
|
||||
' -DOPENSSL_LIBRARIES={path_release}/lib' \
|
||||
' -DWITH_SFTP=ON' \
|
||||
' -DWITH_SERVER=ON' \
|
||||
' -DWITH_STATIC_LIB=ON' \
|
||||
' -DWITH_GSSAPI=OFF' \
|
||||
' -DWITH_ZLIB=OFF' \
|
||||
' -DWITH_STATIC_LIB=ON' \
|
||||
' -DWITH_PCAP=OFF' \
|
||||
' -DWITH_TESTING=OFF' \
|
||||
' -DWITH_CLIENT_TESTING=OFF' \
|
||||
' -DUNIT_TESTING=OFF' \
|
||||
' -DWITH_EXAMPLES=OFF' \
|
||||
' -DWITH_BENCHMARKS=OFF' \
|
||||
' -DWITH_NACL=OFF' \
|
||||
''.format(prefix=self.PATH_RELEASE, oss_ver=env.ver_ossl_number, ossl_inc=env.path_ossl_inc, ossl_lib=env.path_ossl_lib)
|
||||
''.format(path_release=self.PATH_RELEASE)
|
||||
|
||||
try:
|
||||
utils.cmake(build_path, 'Release', False, cmake_define)
|
||||
|
@ -727,32 +685,18 @@ class BuilderMacOS(BuilderBase):
|
|||
# because make install will fail because we can not disable ssh_shared target,
|
||||
# so we copy necessary files ourselves.
|
||||
utils.ensure_file_exists(os.path.join(self.LIBSSH_PATH_SRC, 'build', 'src', 'libssh.a'))
|
||||
utils.ensure_file_exists(os.path.join(self.LIBSSH_PATH_SRC, 'build', 'src', 'threads', 'libssh_threads.a'))
|
||||
# utils.ensure_file_exists(os.path.join(self.LIBSSH_PATH_SRC, 'build', 'src', 'threads', 'libssh_threads.a'))
|
||||
utils.copy_file(os.path.join(self.LIBSSH_PATH_SRC, 'build', 'src'), os.path.join(self.PATH_RELEASE, 'lib'), 'libssh.a')
|
||||
utils.copy_file(os.path.join(self.LIBSSH_PATH_SRC, 'build', 'src', 'threads'), os.path.join(self.PATH_RELEASE, 'lib'), 'libssh_threads.a')
|
||||
# utils.copy_file(os.path.join(self.LIBSSH_PATH_SRC, 'build', 'src', 'threads'), os.path.join(self.PATH_RELEASE, 'lib'), 'libssh_threads.a')
|
||||
utils.copy_ex(os.path.join(self.LIBSSH_PATH_SRC, 'include'), os.path.join(self.PATH_RELEASE, 'include'), 'libssh')
|
||||
|
||||
# def _build_sqlite(self, file_name):
|
||||
# if not os.path.exists(self.SQLITE_PATH_SRC):
|
||||
# os.system('tar -zxvf "{}/{}" -C "{}"'.format(PATH_DOWNLOAD, file_name, self.PATH_TMP))
|
||||
#
|
||||
# cc.n('build sqlite static...', end='')
|
||||
# if os.path.exists(os.path.join(self.PATH_RELEASE, 'lib', 'libsqlite3.a')):
|
||||
# cc.w('already exists, skip.')
|
||||
# return
|
||||
# cc.v('')
|
||||
#
|
||||
# old_p = os.getcwd()
|
||||
# os.chdir(self.SQLITE_PATH_SRC)
|
||||
# os.system('./configure --prefix={}'.format(self.PATH_RELEASE))
|
||||
# os.system('make')
|
||||
# os.system('make install')
|
||||
# os.chdir(old_p)
|
||||
def _prepare_python(self):
|
||||
pass
|
||||
|
||||
def fix_output(self):
|
||||
# remove .so files, otherwise will link to .so but not .a in default.
|
||||
# rm = ['libsqlite3.la', 'libsqlite3.so.0', 'libsqlite3.so', 'libsqlite3.so.0.8.6', 'libuv.la', 'libuv.so.1', 'libuv.so', 'libuv.so.1.0.0']
|
||||
rm = ['libuv.la', 'libuv.so.1', 'libuv.so', 'libuv.so.1.0.0']
|
||||
rm = ['libuv.la', 'libuv.dylib', 'libuv.so.1', 'libuv.so', 'libuv.so.1.0.0']
|
||||
for i in rm:
|
||||
_path = os.path.join(self.PATH_RELEASE, 'lib', i)
|
||||
if os.path.exists(_path):
|
||||
|
|
|
@ -10,7 +10,7 @@ on CommandRun(theCmd, theProfile, theTitle)
|
|||
if it is not running then
|
||||
tell application "iTerm"
|
||||
activate
|
||||
delay 0.2
|
||||
delay 0.5
|
||||
try
|
||||
close first window
|
||||
end try
|
||||
|
@ -24,9 +24,12 @@ on CommandRun(theCmd, theProfile, theTitle)
|
|||
end try
|
||||
tell the current window
|
||||
tell the current session
|
||||
delay 0.5
|
||||
set name to theTitle
|
||||
set profile to theProfile
|
||||
write text theCmd
|
||||
delay 0.5
|
||||
write text "useless"
|
||||
end tell
|
||||
end tell
|
||||
end tell
|
||||
|
@ -43,14 +46,18 @@ on CommandRun(theCmd, theProfile, theTitle)
|
|||
end try
|
||||
tell the current tab
|
||||
tell the current session
|
||||
delay 0.5
|
||||
set name to theTitle
|
||||
write text theCmd
|
||||
delay 0.5
|
||||
write text "useless"
|
||||
end tell
|
||||
end tell
|
||||
end tell
|
||||
end tell
|
||||
on error msg
|
||||
--if all iTerm windows are closed the app stays open. In this scenario iTerm has no "current window" and will give an error when trying to create the new tab.
|
||||
-- if all iTerm windows are closed the app stays open. In this scenario iTerm has
|
||||
-- no "current window" and will give an error when trying to create the new tab.
|
||||
tell application "iTerm"
|
||||
try
|
||||
create window with profile theProfile
|
||||
|
@ -59,8 +66,11 @@ on CommandRun(theCmd, theProfile, theTitle)
|
|||
end try
|
||||
tell the current window
|
||||
tell the current session
|
||||
delay 0.5
|
||||
set name to theTitle
|
||||
write text theCmd
|
||||
delay 0.5
|
||||
write text "useless"
|
||||
end tell
|
||||
end tell
|
||||
end tell
|
||||
|
|
|
@ -69,6 +69,7 @@
|
|||
<div class="col-sm-8">
|
||||
<input id="rdp-app" type="text" class="form-control input-args"/>
|
||||
<span class="desc"><i class="fa fa-info-circle"></i> 建议使用homebrew安装freerdp,安装后freerdp默认路径在:/usr/local/Cellar/freerdp/x.y.z/bin/xfreerdp</span>
|
||||
<span class="desc"><i class="fa fa-info-circle"></i> 首次安装freerdp后需要重新启动计算机</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
// tp_assist
|
||||
//
|
||||
// Created by ApexLiu on 2017/9/29.
|
||||
// Copyright © 2017年 eomsoft. All rights reserved.
|
||||
//
|
||||
|
||||
#include "AppDelegate-C-Interface.h"
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
// tp_assist
|
||||
//
|
||||
// Created by ApexLiu on 2017/9/27.
|
||||
// Copyright © 2017年 eomsoft. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef wrap_c_objc_h
|
||||
|
|
|
@ -167,7 +167,7 @@ int AppDelegate_start_ssh_client (void *_self, const char* cmd_line, const char*
|
|||
|
||||
- (IBAction)visitWebsite:(id)sender {
|
||||
|
||||
NSURL *url = [NSURL URLWithString:@"http://www.tp4a.com/"];
|
||||
NSURL *url = [NSURL URLWithString:@"https://www.tp4a.com/"];
|
||||
[[NSWorkspace sharedWorkspace] openURL:url];
|
||||
}
|
||||
|
||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -1,8 +1,7 @@
|
|||
#ifndef __TS_CONST_H__
|
||||
#define __TS_CONST_H__
|
||||
|
||||
//#define TS_WEB_URL L"http://teleport.eomsoft.net/"
|
||||
//#define TS_BBS_URL L"http://bbs.eomsoft.net/"
|
||||
//#define TS_WEB_URL L"https://www.tp4a.com/"
|
||||
//#define TS_TRAY_MSG L"Teleport助手正常工作中"
|
||||
|
||||
#define TS_HTTP_RPC_PORT 50022
|
||||
|
|
|
@ -25,7 +25,7 @@ bool TsEnv::init(const char* cfg_file, const char* res_path)
|
|||
ex_astr2wstr(res_path, m_res_path);
|
||||
|
||||
//#ifdef EX_DEBUG
|
||||
// m_site_path = L"/Users/apex/work/eomsoft/teleport-dev/client/tp_assist_macos/site";
|
||||
// m_site_path = L"/Users/apex/work/tp4a/teleport/client/tp_assist_macos/site";
|
||||
//#else
|
||||
m_site_path = m_res_path;
|
||||
ex_path_join(m_site_path, false, L"site", NULL);
|
||||
|
|
|
@ -29,12 +29,12 @@
|
|||
<key>LSUIElement</key>
|
||||
<true/>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>Copyright © 2017~2018 EOMSOFT. All rights reserved.</string>
|
||||
<string>Copyright © 2017~2018 TP4A. All rights reserved.</string>
|
||||
<key>NSMainNibFile</key>
|
||||
<string>MainMenu</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>NSApplication</string>
|
||||
<key>Product Homepage</key>
|
||||
<string>http://teleport.eomsoft.net/</string>
|
||||
<string>https://www.tp4a.com/</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
@ -9,12 +9,13 @@
|
|||
/* Begin PBXBuildFile section */
|
||||
0ADB3B0C178EF8DB004E9BB9 /* StatusIconAlt.png in Resources */ = {isa = PBXBuildFile; fileRef = 0ADB3B08178EF8DB004E9BB9 /* StatusIconAlt.png */; };
|
||||
0ADB3B0D178EF8DB004E9BB9 /* StatusIcon.png in Resources */ = {isa = PBXBuildFile; fileRef = 0ADB3B09178EF8DB004E9BB9 /* StatusIcon.png */; };
|
||||
0ADB3B0E178EF8DB004E9BB9 /* StatusIcon@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 0ADB3B0A178EF8DB004E9BB9 /* StatusIcon@2x.png */; };
|
||||
0ADB3B0F178EF8DB004E9BB9 /* StatusIconAlt@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 0ADB3B0B178EF8DB004E9BB9 /* StatusIconAlt@2x.png */; };
|
||||
7A0C94AA1F68BD2900E04C3E /* AboutWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6FC541A51D45CF7F00A896E3 /* AboutWindowController.xib */; };
|
||||
7A18188F1F7D5D7F00F3C882 /* AppDelegate-C-Interface.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7A18188E1F7D5D7F00F3C882 /* AppDelegate-C-Interface.cpp */; };
|
||||
7A1818911F7FBBC200F3C882 /* tp-assist.default.json in Resources */ = {isa = PBXBuildFile; fileRef = 7A1818901F7FBBC200F3C882 /* tp-assist.default.json */; };
|
||||
7A1818931F815B8A00F3C882 /* Terminal.scpt in Resources */ = {isa = PBXBuildFile; fileRef = 7A1818921F815B8A00F3C882 /* Terminal.scpt */; };
|
||||
7A1F87AD215D59BD00B69F88 /* Terminal.scpt in Resources */ = {isa = PBXBuildFile; fileRef = 7A1F87AB215D59BD00B69F88 /* Terminal.scpt */; };
|
||||
7A1F87AE215D59BD00B69F88 /* iTerm2.scpt in Resources */ = {isa = PBXBuildFile; fileRef = 7A1F87AC215D59BD00B69F88 /* iTerm2.scpt */; };
|
||||
7A1F87B1215D5A1600B69F88 /* StatusIconAlt@2X.png in Resources */ = {isa = PBXBuildFile; fileRef = 7A1F87AF215D5A1600B69F88 /* StatusIconAlt@2X.png */; };
|
||||
7A1F87B2215D5A1600B69F88 /* StatusIcon@2X.png in Resources */ = {isa = PBXBuildFile; fileRef = 7A1F87B0215D5A1600B69F88 /* StatusIcon@2X.png */; };
|
||||
7A27E4A91F6A8EEC004FDE5D /* ts_env.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7A27E4A71F6A8EEC004FDE5D /* ts_env.cpp */; };
|
||||
7AA2CD381F6A92620074C92B /* ts_http_rpc.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7AA2CD371F6A92620074C92B /* ts_http_rpc.cpp */; };
|
||||
7AA2CD3B1F6A955A0074C92B /* ts_cfg.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7AA2CD391F6A955A0074C92B /* ts_cfg.cpp */; };
|
||||
|
@ -30,7 +31,6 @@
|
|||
7AA2CD541F6AB9F10074C92B /* json_writer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7AA2CD501F6AB9F10074C92B /* json_writer.cpp */; };
|
||||
7AA2CD571F6ABA2E0074C92B /* mongoose.c in Sources */ = {isa = PBXBuildFile; fileRef = 7AA2CD561F6ABA2E0074C92B /* mongoose.c */; };
|
||||
7AA2CD591F6AC0DA0074C92B /* site in Resources */ = {isa = PBXBuildFile; fileRef = 7AA2CD581F6AC0DA0074C92B /* site */; };
|
||||
7AD1F1D31F7A55EA0048A496 /* iTerm2.scpt in Resources */ = {isa = PBXBuildFile; fileRef = 7AD1F1D11F7A55EA0048A496 /* iTerm2.scpt */; };
|
||||
A1B7B9DD1DB53ED200809327 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = A1B7B9DF1DB53ED200809327 /* Localizable.strings */; };
|
||||
A1D700071A5DCE8D003563E4 /* AboutWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = A1D700061A5DCE8D003563E4 /* AboutWindowController.m */; };
|
||||
C149EBFE15D5214600B1F558 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C149EBFD15D5214600B1F558 /* Cocoa.framework */; };
|
||||
|
@ -44,17 +44,37 @@
|
|||
/* Begin PBXFileReference section */
|
||||
0ADB3B08178EF8DB004E9BB9 /* StatusIconAlt.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = StatusIconAlt.png; sourceTree = "<group>"; };
|
||||
0ADB3B09178EF8DB004E9BB9 /* StatusIcon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = StatusIcon.png; sourceTree = "<group>"; };
|
||||
0ADB3B0A178EF8DB004E9BB9 /* StatusIcon@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "StatusIcon@2x.png"; sourceTree = "<group>"; };
|
||||
0ADB3B0B178EF8DB004E9BB9 /* StatusIconAlt@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "StatusIconAlt@2x.png"; sourceTree = "<group>"; };
|
||||
7A18188E1F7D5D7F00F3C882 /* AppDelegate-C-Interface.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = "AppDelegate-C-Interface.cpp"; sourceTree = "<group>"; };
|
||||
7A1818901F7FBBC200F3C882 /* tp-assist.default.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "tp-assist.default.json"; sourceTree = "<group>"; };
|
||||
7A1818921F815B8A00F3C882 /* Terminal.scpt */ = {isa = PBXFileReference; lastKnownFileType = file; path = Terminal.scpt; sourceTree = "<group>"; };
|
||||
7A1818951F8242E900F3C882 /* apple-scripts */ = {isa = PBXFileReference; lastKnownFileType = folder; path = "apple-scripts"; sourceTree = "<group>"; };
|
||||
7A1F8797215D565600B69F88 /* ex_util.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; name = ex_util.h; path = ../../../../common/libex/include/ex/ex_util.h; sourceTree = "<group>"; };
|
||||
7A1F8798215D565600B69F88 /* ex_ini.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; name = ex_ini.h; path = ../../../../common/libex/include/ex/ex_ini.h; sourceTree = "<group>"; };
|
||||
7A1F8799215D565600B69F88 /* ex_const.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; name = ex_const.h; path = ../../../../common/libex/include/ex/ex_const.h; sourceTree = "<group>"; };
|
||||
7A1F879B215D565600B69F88 /* ex_log.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; name = ex_log.h; path = ../../../../common/libex/include/ex/ex_log.h; sourceTree = "<group>"; };
|
||||
7A1F879C215D565600B69F88 /* ex_platform.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; name = ex_platform.h; path = ../../../../common/libex/include/ex/ex_platform.h; sourceTree = "<group>"; };
|
||||
7A1F879D215D565600B69F88 /* ex_types.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; name = ex_types.h; path = ../../../../common/libex/include/ex/ex_types.h; sourceTree = "<group>"; };
|
||||
7A1F879E215D565700B69F88 /* ex_str.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; name = ex_str.h; path = ../../../../common/libex/include/ex/ex_str.h; sourceTree = "<group>"; };
|
||||
7A1F879F215D565700B69F88 /* ex_path.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; name = ex_path.h; path = ../../../../common/libex/include/ex/ex_path.h; sourceTree = "<group>"; };
|
||||
7A1F87A0215D565700B69F88 /* ex_thread.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; name = ex_thread.h; path = ../../../../common/libex/include/ex/ex_thread.h; sourceTree = "<group>"; };
|
||||
7A1F87A1215D56B500B69F88 /* writer.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; name = writer.h; path = ../../../../external/jsoncpp/include/json/writer.h; sourceTree = "<group>"; };
|
||||
7A1F87A2215D570000B69F88 /* value.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; name = value.h; path = ../../../../external/jsoncpp/include/json/value.h; sourceTree = "<group>"; };
|
||||
7A1F87A3215D570000B69F88 /* reader.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; name = reader.h; path = ../../../../external/jsoncpp/include/json/reader.h; sourceTree = "<group>"; };
|
||||
7A1F87A4215D570000B69F88 /* json.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; name = json.h; path = ../../../../external/jsoncpp/include/json/json.h; sourceTree = "<group>"; };
|
||||
7A1F87A5215D574400B69F88 /* config.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; name = config.h; path = ../../../../external/jsoncpp/include/json/config.h; sourceTree = "<group>"; };
|
||||
7A1F87A6215D574500B69F88 /* features.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; name = features.h; path = ../../../../external/jsoncpp/include/json/features.h; sourceTree = "<group>"; };
|
||||
7A1F87A7215D574500B69F88 /* forwards.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; name = forwards.h; path = ../../../../external/jsoncpp/include/json/forwards.h; sourceTree = "<group>"; };
|
||||
7A1F87A8215D574500B69F88 /* assertions.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; name = assertions.h; path = ../../../../external/jsoncpp/include/json/assertions.h; sourceTree = "<group>"; };
|
||||
7A1F87A9215D574500B69F88 /* autolink.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; name = autolink.h; path = ../../../../external/jsoncpp/include/json/autolink.h; sourceTree = "<group>"; };
|
||||
7A1F87AA215D574500B69F88 /* version.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; name = version.h; path = ../../../../external/jsoncpp/include/json/version.h; sourceTree = "<group>"; };
|
||||
7A1F87AB215D59BD00B69F88 /* Terminal.scpt */ = {isa = PBXFileReference; lastKnownFileType = file; path = Terminal.scpt; sourceTree = "<group>"; };
|
||||
7A1F87AC215D59BD00B69F88 /* iTerm2.scpt */ = {isa = PBXFileReference; lastKnownFileType = file; path = iTerm2.scpt; sourceTree = "<group>"; };
|
||||
7A1F87AF215D5A1600B69F88 /* StatusIconAlt@2X.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "StatusIconAlt@2X.png"; sourceTree = "<group>"; };
|
||||
7A1F87B0215D5A1600B69F88 /* StatusIcon@2X.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "StatusIcon@2X.png"; sourceTree = "<group>"; };
|
||||
7A27E4A61F6A899B004FDE5D /* ts_const.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ts_const.h; sourceTree = "<group>"; };
|
||||
7A27E4A71F6A8EEC004FDE5D /* ts_env.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ts_env.cpp; sourceTree = "<group>"; };
|
||||
7A27E4A81F6A8EEC004FDE5D /* ts_env.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ts_env.h; sourceTree = "<group>"; };
|
||||
7A40FFE21F7B2A4500F11697 /* AppDelegate-C-Interface.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "AppDelegate-C-Interface.h"; sourceTree = "<group>"; };
|
||||
7AA2CD361F6A92380074C92B /* ts_http_rpc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ts_http_rpc.h; sourceTree = "<group>"; };
|
||||
7AA2CD361F6A92380074C92B /* ts_http_rpc.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 2147486000; path = ts_http_rpc.h; sourceTree = "<group>"; };
|
||||
7AA2CD371F6A92620074C92B /* ts_http_rpc.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ts_http_rpc.cpp; sourceTree = "<group>"; };
|
||||
7AA2CD391F6A955A0074C92B /* ts_cfg.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ts_cfg.cpp; sourceTree = "<group>"; };
|
||||
7AA2CD3A1F6A955A0074C92B /* ts_cfg.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ts_cfg.h; sourceTree = "<group>"; };
|
||||
|
@ -66,13 +86,12 @@
|
|||
7AA2CD421F6AB9750074C92B /* ex_util.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ex_util.cpp; path = ../../../../common/libex/src/ex_util.cpp; sourceTree = "<group>"; };
|
||||
7AA2CD431F6AB9750074C92B /* ex_winsrv.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ex_winsrv.cpp; path = ../../../../common/libex/src/ex_winsrv.cpp; sourceTree = "<group>"; };
|
||||
7AA2CD4C1F6AB9F10074C92B /* json_reader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = json_reader.cpp; path = ../../../../external/jsoncpp/src/lib_json/json_reader.cpp; sourceTree = "<group>"; };
|
||||
7AA2CD4D1F6AB9F10074C92B /* json_tool.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = json_tool.h; path = ../../../../external/jsoncpp/src/lib_json/json_tool.h; sourceTree = "<group>"; };
|
||||
7AA2CD4D1F6AB9F10074C92B /* json_tool.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; name = json_tool.h; path = ../../../../external/jsoncpp/src/lib_json/json_tool.h; sourceTree = "<group>"; };
|
||||
7AA2CD4E1F6AB9F10074C92B /* json_value.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = json_value.cpp; path = ../../../../external/jsoncpp/src/lib_json/json_value.cpp; sourceTree = "<group>"; };
|
||||
7AA2CD4F1F6AB9F10074C92B /* json_valueiterator.inl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = json_valueiterator.inl; path = ../../../../external/jsoncpp/src/lib_json/json_valueiterator.inl; sourceTree = "<group>"; };
|
||||
7AA2CD501F6AB9F10074C92B /* json_writer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = json_writer.cpp; path = ../../../../external/jsoncpp/src/lib_json/json_writer.cpp; sourceTree = "<group>"; };
|
||||
7AA2CD561F6ABA2E0074C92B /* mongoose.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = mongoose.c; path = ../../../../external/mongoose/mongoose.c; sourceTree = "<group>"; };
|
||||
7AA2CD581F6AC0DA0074C92B /* site */ = {isa = PBXFileReference; lastKnownFileType = folder; path = site; sourceTree = "<group>"; };
|
||||
7AD1F1D11F7A55EA0048A496 /* iTerm2.scpt */ = {isa = PBXFileReference; lastKnownFileType = file; path = iTerm2.scpt; sourceTree = "<group>"; };
|
||||
A1B7B9D31DB5361700809327 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
|
||||
A1B7B9DE1DB53ED200809327 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
A1B7B9E01DB53ED700809327 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
|
@ -113,10 +132,10 @@
|
|||
0ADB3B10178EF8E2004E9BB9 /* Images */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7A1F87B0215D5A1600B69F88 /* StatusIcon@2X.png */,
|
||||
7A1F87AF215D5A1600B69F88 /* StatusIconAlt@2X.png */,
|
||||
0ADB3B09178EF8DB004E9BB9 /* StatusIcon.png */,
|
||||
0ADB3B0A178EF8DB004E9BB9 /* StatusIcon@2x.png */,
|
||||
0ADB3B08178EF8DB004E9BB9 /* StatusIconAlt.png */,
|
||||
0ADB3B0B178EF8DB004E9BB9 /* StatusIconAlt@2x.png */,
|
||||
);
|
||||
name = Images;
|
||||
sourceTree = "<group>";
|
||||
|
@ -124,6 +143,15 @@
|
|||
7AA2CD3C1F6AB9560074C92B /* libex */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7A1F8799215D565600B69F88 /* ex_const.h */,
|
||||
7A1F8798215D565600B69F88 /* ex_ini.h */,
|
||||
7A1F879B215D565600B69F88 /* ex_log.h */,
|
||||
7A1F879F215D565700B69F88 /* ex_path.h */,
|
||||
7A1F879C215D565600B69F88 /* ex_platform.h */,
|
||||
7A1F879E215D565700B69F88 /* ex_str.h */,
|
||||
7A1F87A0215D565700B69F88 /* ex_thread.h */,
|
||||
7A1F879D215D565600B69F88 /* ex_types.h */,
|
||||
7A1F8797215D565600B69F88 /* ex_util.h */,
|
||||
7AA2CD3D1F6AB9750074C92B /* ex_ini.cpp */,
|
||||
7AA2CD3E1F6AB9750074C92B /* ex_log.cpp */,
|
||||
7AA2CD3F1F6AB9750074C92B /* ex_path.cpp */,
|
||||
|
@ -138,6 +166,16 @@
|
|||
7AA2CD4B1F6AB9880074C92B /* jsoncpp */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7A1F87A8215D574500B69F88 /* assertions.h */,
|
||||
7A1F87A9215D574500B69F88 /* autolink.h */,
|
||||
7A1F87A5215D574400B69F88 /* config.h */,
|
||||
7A1F87A6215D574500B69F88 /* features.h */,
|
||||
7A1F87A7215D574500B69F88 /* forwards.h */,
|
||||
7A1F87AA215D574500B69F88 /* version.h */,
|
||||
7A1F87A4215D570000B69F88 /* json.h */,
|
||||
7A1F87A3215D570000B69F88 /* reader.h */,
|
||||
7A1F87A2215D570000B69F88 /* value.h */,
|
||||
7A1F87A1215D56B500B69F88 /* writer.h */,
|
||||
7AA2CD4C1F6AB9F10074C92B /* json_reader.cpp */,
|
||||
7AA2CD4D1F6AB9F10074C92B /* json_tool.h */,
|
||||
7AA2CD4E1F6AB9F10074C92B /* json_value.cpp */,
|
||||
|
@ -175,8 +213,8 @@
|
|||
A12D9BE61BCF2C72004F52A6 /* apple-scpt */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7A1818921F815B8A00F3C882 /* Terminal.scpt */,
|
||||
7AD1F1D11F7A55EA0048A496 /* iTerm2.scpt */,
|
||||
7A1F87AC215D59BD00B69F88 /* iTerm2.scpt */,
|
||||
7A1F87AB215D59BD00B69F88 /* Terminal.scpt */,
|
||||
);
|
||||
path = "apple-scpt";
|
||||
sourceTree = "<group>";
|
||||
|
@ -308,16 +346,16 @@
|
|||
7AA2CD591F6AC0DA0074C92B /* site in Resources */,
|
||||
A1B7B9DD1DB53ED200809327 /* Localizable.strings in Resources */,
|
||||
0ADB3B0D178EF8DB004E9BB9 /* StatusIcon.png in Resources */,
|
||||
7A1F87B1215D5A1600B69F88 /* StatusIconAlt@2X.png in Resources */,
|
||||
7A1F87AE215D59BD00B69F88 /* iTerm2.scpt in Resources */,
|
||||
0ADB3B0C178EF8DB004E9BB9 /* StatusIconAlt.png in Resources */,
|
||||
C149EC0815D5214600B1F558 /* InfoPlist.strings in Resources */,
|
||||
0ADB3B0F178EF8DB004E9BB9 /* StatusIconAlt@2x.png in Resources */,
|
||||
7AD1F1D31F7A55EA0048A496 /* iTerm2.scpt in Resources */,
|
||||
0ADB3B0E178EF8DB004E9BB9 /* StatusIcon@2x.png in Resources */,
|
||||
7A1F87AD215D59BD00B69F88 /* Terminal.scpt in Resources */,
|
||||
C149EC1415D5214600B1F558 /* MainMenu.xib in Resources */,
|
||||
C159DC2815D5DE8000F5DE24 /* teleport.icns in Resources */,
|
||||
7A0C94AA1F68BD2900E04C3E /* AboutWindowController.xib in Resources */,
|
||||
7A1818911F7FBBC200F3C882 /* tp-assist.default.json in Resources */,
|
||||
7A1818931F815B8A00F3C882 /* Terminal.scpt in Resources */,
|
||||
7A1F87B2215D5A1600B69F88 /* StatusIcon@2X.png in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -399,6 +437,7 @@
|
|||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
|
@ -454,6 +493,7 @@
|
|||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
|
|
|
@ -35,7 +35,7 @@ wchar_t* ex_abspath_to(const wchar_t* base_abs_path, const wchar_t* relate_path)
|
|||
bool ex_exec_file(ex_wstr& out_filename);
|
||||
bool ex_abspath(ex_wstr& inout_path);
|
||||
bool ex_dirname(ex_wstr& inout_filename);
|
||||
bool ex_path_join(ex_wstr& inout_path, bool auto_abspath, ...);
|
||||
bool ex_path_join(ex_wstr& inout_path, EX_BOOL auto_abspath, ...);
|
||||
bool ex_abspath_to(const ex_wstr& base_abs_path, const ex_wstr& relate_path, ex_wstr& out_path);
|
||||
bool ex_mkdirs(const ex_wstr& in_path);
|
||||
|
||||
|
|
|
@ -409,7 +409,7 @@ bool ex_abspath(ex_wstr& inout_path)
|
|||
return true;
|
||||
}
|
||||
|
||||
bool ex_path_join(ex_wstr& inout_path, bool auto_abspath, ...)
|
||||
bool ex_path_join(ex_wstr& inout_path, EX_BOOL auto_abspath, ...)
|
||||
{
|
||||
wchar_t* tmp;
|
||||
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
teleport
|
|
@ -1,32 +1,37 @@
|
|||
MESSAGE(STATUS "operation system is ${CMAKE_SYSTEM}")
|
||||
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
#project(teleport)
|
||||
project(teleport)
|
||||
|
||||
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
|
||||
MESSAGE(STATUS "operation system is ${CMAKE_SYSTEM}")
|
||||
MESSAGE(STATUS "current source directory is ${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
|
||||
#set(SOURCE_FILES main.cpp)
|
||||
#add_executable(teleport ${SOURCE_FILES})
|
||||
#set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${teleport_SOURCE_DIR}/../out/server/x64/bin")
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${teleport_SOURCE_DIR}/../out/server/x64/bin")
|
||||
|
||||
set(CMAKE_CONFIGURATION_TYPES Debug Release)
|
||||
|
||||
IF (CMAKE_SYSTEM_NAME MATCHES "Linux")
|
||||
# Determine the platform.
|
||||
if("${CMAKE_SYSTEM_NAME}" STREQUAL "Darwin")
|
||||
MESSAGE(STATUS "build on macOS...")
|
||||
set(OS_MACOS 1)
|
||||
set(OS_POSIX 1)
|
||||
set(TP_EXTERNAL_RELEASE_DIR "${teleport_SOURCE_DIR}/../external/macos/release")
|
||||
elseif("${CMAKE_SYSTEM_NAME}" STREQUAL "Linux")
|
||||
set(OS_LINUX 1)
|
||||
set(OS_POSIX 1)
|
||||
MESSAGE(STATUS "build on Linux...")
|
||||
add_subdirectory(tp_web/src)
|
||||
ELSEIF (CMAKE_SYSTEM_NAME MATCHES "Windows")
|
||||
set(TP_EXTERNAL_RELEASE_DIR "${teleport_SOURCE_DIR}/../external/linux/release")
|
||||
elseif("${CMAKE_SYSTEM_NAME}" STREQUAL "Windows")
|
||||
MESSAGE(FATAL_ERROR "unsupported platform: Windows")
|
||||
ELSEIF (CMAKE_SYSTEM_NAME MATCHES "Darwin")
|
||||
MESSAGE(STATUS "build on MacOS...")
|
||||
ELSE ()
|
||||
else()
|
||||
MESSAGE(FATAL_ERROR "unsupported platform: ${CMAKE_SYSTEM_NAME}")
|
||||
ENDIF (CMAKE_SYSTEM_NAME MATCHES "Linux")
|
||||
endif()
|
||||
|
||||
add_subdirectory(tp_core/core)
|
||||
add_subdirectory(tp_core/protocol/ssh)
|
||||
add_subdirectory(tp_core/protocol/telnet)
|
||||
#add_subdirectory(testssh/testssh)
|
||||
|
||||
IF (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/tp_core/protocol/rdp")
|
||||
if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/tp_core/protocol/rdp")
|
||||
add_subdirectory(tp_core/protocol/rdp)
|
||||
ENDIF (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/tp_core/protocol/rdp")
|
||||
endif()
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
cmake_minimum_required(VERSION 3.5)
|
||||
project(tpcore)
|
||||
|
||||
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
|
||||
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${Project_SOURCE_DIR}/../out/server/x64/bin")
|
||||
|
||||
MESSAGE(STATUS "=======================================================")
|
||||
MESSAGE(STATUS " tp_core")
|
||||
MESSAGE(STATUS "=======================================================")
|
||||
MESSAGE(STATUS "operation system is ${CMAKE_SYSTEM}")
|
||||
MESSAGE(STATUS "current source directory is ${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
|
||||
ADD_DEFINITIONS(
|
||||
-DMG_ENABLE_THREADS
|
||||
|
@ -22,9 +22,6 @@ aux_source_directory(../../../common/libex/src DIR_SRCS)
|
|||
aux_source_directory(../../../external/mongoose DIR_SRCS)
|
||||
aux_source_directory(../../../external/jsoncpp/src/lib_json DIR_SRCS)
|
||||
|
||||
|
||||
#list(REMOVE_ITEM DIR_SRCS "./src/ts_win_service_helper.cpp")
|
||||
|
||||
include_directories(
|
||||
../../../common/libex/include
|
||||
../../../common/teleport
|
||||
|
@ -32,24 +29,17 @@ include_directories(
|
|||
../../../external/jsoncpp/include
|
||||
)
|
||||
|
||||
IF (CMAKE_SYSTEM_NAME MATCHES "Linux")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "-export-dynamic")
|
||||
include_directories(
|
||||
../../../external/linux/release/include
|
||||
${TP_EXTERNAL_RELEASE_DIR}/include
|
||||
)
|
||||
link_directories(../../../external/linux/release/lib)
|
||||
ELSEIF (CMAKE_SYSTEM_NAME MATCHES "Darwin")
|
||||
include_directories(
|
||||
../../../external/macos/release/include
|
||||
)
|
||||
link_directories(../../../external/macos/release/lib)
|
||||
ENDIF (CMAKE_SYSTEM_NAME MATCHES "Linux")
|
||||
link_directories(${TP_EXTERNAL_RELEASE_DIR}/lib)
|
||||
|
||||
|
||||
add_executable(tp_core ${DIR_SRCS})
|
||||
|
||||
IF (CMAKE_SYSTEM_NAME MATCHES "Linux")
|
||||
if (OS_LINUX)
|
||||
set(CMAKE_EXE_LINKER_FLAGS "-export-dynamic")
|
||||
target_link_libraries(tp_core ssl crypto mbedx509 mbedtls mbedcrypto dl pthread rt util)
|
||||
ELSEIF (CMAKE_SYSTEM_NAME MATCHES "Darwin")
|
||||
elseif (OS_MACOS)
|
||||
target_link_libraries(tp_core ssl crypto mbedx509 mbedtls mbedcrypto dl pthread util)
|
||||
ENDIF (CMAKE_SYSTEM_NAME MATCHES "Linux")
|
||||
endif ()
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
cmake_minimum_required(VERSION 3.5)
|
||||
project(tpssh)
|
||||
|
||||
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC")
|
||||
#set(CMAKE_CFLAGS "${CMAKE_CFLAGS} -fPIC")
|
||||
MESSAGE(STATUS "=======================================================")
|
||||
MESSAGE(STATUS " libtpssh")
|
||||
MESSAGE(STATUS "=======================================================")
|
||||
MESSAGE(STATUS "operation system is ${CMAKE_SYSTEM}")
|
||||
MESSAGE(STATUS "current source directory is ${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
|
||||
set(CMAKE_CXX_FLAGS "-fPIC")
|
||||
set(CMAKE_C_FLAGS "-fPIC")
|
||||
|
||||
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${Project_SOURCE_DIR}/../out/server/x64/bin")
|
||||
|
||||
|
||||
aux_source_directory(. DIR_SSH_SRCS)
|
||||
aux_source_directory(../../common DIR_SSH_SRCS)
|
||||
aux_source_directory(../../../../common/libex/src DIR_SSH_SRCS)
|
||||
|
@ -24,25 +24,16 @@ include_directories(
|
|||
../../../../external/jsoncpp/include
|
||||
)
|
||||
|
||||
IF (CMAKE_SYSTEM_NAME MATCHES "Linux")
|
||||
include_directories(
|
||||
../../../../external/linux/release/include
|
||||
${TP_EXTERNAL_RELEASE_DIR}/include
|
||||
)
|
||||
link_directories(../../../../external/linux/release/lib)
|
||||
ELSEIF (CMAKE_SYSTEM_NAME MATCHES "Darwin")
|
||||
include_directories(
|
||||
../../../../external/macos/release/include
|
||||
)
|
||||
link_directories(../../../../external/macos/release/lib)
|
||||
ENDIF (CMAKE_SYSTEM_NAME MATCHES "Linux")
|
||||
link_directories(${TP_EXTERNAL_RELEASE_DIR}/lib)
|
||||
|
||||
|
||||
add_library(tpssh SHARED ${DIR_SSH_SRCS})
|
||||
|
||||
IF (CMAKE_SYSTEM_NAME MATCHES "Linux")
|
||||
# target_link_libraries(tpssh ssh ssh_threads ssl crypto mbedx509 mbedtls mbedcrypto dl pthread rt util)
|
||||
if (OS_LINUX)
|
||||
target_link_libraries(tpssh ssh ssl crypto mbedx509 mbedtls mbedcrypto dl pthread rt util)
|
||||
ELSEIF (CMAKE_SYSTEM_NAME MATCHES "Darwin")
|
||||
target_link_libraries(tpssh ssh ssh_threads ssl crypto mbedx509 mbedtls mbedcrypto dl pthread util)
|
||||
ENDIF (CMAKE_SYSTEM_NAME MATCHES "Linux")
|
||||
|
||||
elseif (OS_MACOS)
|
||||
target_link_libraries(tpssh ssh ssl crypto mbedx509 mbedtls mbedcrypto dl pthread util)
|
||||
endif()
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
cmake_minimum_required(VERSION 3.5)
|
||||
project(tptelnet)
|
||||
|
||||
MESSAGE(STATUS "=======================================================")
|
||||
MESSAGE(STATUS " libtptelnet")
|
||||
MESSAGE(STATUS "=======================================================")
|
||||
MESSAGE(STATUS "operation system is ${CMAKE_SYSTEM}")
|
||||
MESSAGE(STATUS "current source directory is ${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC")
|
||||
|
||||
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${Project_SOURCE_DIR}/../out/server/x64/bin")
|
||||
|
||||
|
||||
aux_source_directory(. DIR_TELNET_SRCS)
|
||||
aux_source_directory(../../common DIR_TELNET_SRCS)
|
||||
aux_source_directory(../../../../common/libex/src DIR_TELNET_SRCS)
|
||||
|
@ -21,26 +23,15 @@ include_directories(
|
|||
../../../../external/jsoncpp/include
|
||||
)
|
||||
|
||||
IF (CMAKE_SYSTEM_NAME MATCHES "Linux")
|
||||
include_directories(
|
||||
../../../../external/linux/release/include
|
||||
${TP_EXTERNAL_RELEASE_DIR}/include
|
||||
)
|
||||
link_directories(../../../../external/linux/release/lib)
|
||||
ELSEIF (CMAKE_SYSTEM_NAME MATCHES "Darwin")
|
||||
include_directories(
|
||||
../../../../external/macos/release/include
|
||||
)
|
||||
link_directories(../../../../external/macos/release/lib)
|
||||
ENDIF (CMAKE_SYSTEM_NAME MATCHES "Linux")
|
||||
|
||||
link_directories(${TP_EXTERNAL_RELEASE_DIR}/lib)
|
||||
|
||||
add_library(tptelnet SHARED ${DIR_TELNET_SRCS})
|
||||
|
||||
IF (CMAKE_SYSTEM_NAME MATCHES "Linux")
|
||||
#target_link_libraries(tptelnet uv mbedx509 mbedtls mbedcrypto dl pthread rt util)
|
||||
if (OS_LINUX)
|
||||
target_link_libraries(tptelnet uv dl pthread rt util)
|
||||
ELSEIF (CMAKE_SYSTEM_NAME MATCHES "Darwin")
|
||||
#target_link_libraries(tptelnet uv mbedx509 mbedtls mbedcrypto dl pthread util)
|
||||
elseif (OS_MACOS)
|
||||
target_link_libraries(tptelnet uv dl pthread util)
|
||||
ENDIF (CMAKE_SYSTEM_NAME MATCHES "Linux")
|
||||
|
||||
endif()
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
cmake_minimum_required(VERSION 3.5)
|
||||
project(tpweb)
|
||||
|
||||
MESSAGE(STATUS "=======================================================")
|
||||
MESSAGE(STATUS " libtptelnet")
|
||||
MESSAGE(STATUS "=======================================================")
|
||||
MESSAGE(STATUS "operation system is ${CMAKE_SYSTEM}")
|
||||
MESSAGE(STATUS "current source directory is ${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
|
||||
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
|
||||
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${Project_SOURCE_DIR}/../out/server/x64/bin")
|
||||
|
||||
set(CMAKE_EXE_LINKER_FLAGS "-export-dynamic")
|
||||
|
||||
aux_source_directory(. DIR_SRCS)
|
||||
|
@ -14,12 +17,13 @@ aux_source_directory(../../../common/pyshell/src DIR_SRCS)
|
|||
include_directories(
|
||||
../../../common/libex/include
|
||||
../../../common/pyshell/include
|
||||
../../../external/linux/release/include
|
||||
../../../external/linux/release/include/python
|
||||
)
|
||||
|
||||
link_directories(../../../external/linux/release/lib)
|
||||
include_directories(
|
||||
${TP_EXTERNAL_RELEASE_DIR}/include
|
||||
${TP_EXTERNAL_RELEASE_DIR}/include/python
|
||||
)
|
||||
link_directories(${TP_EXTERNAL_RELEASE_DIR}/lib)
|
||||
|
||||
add_executable(tp_web ${DIR_SRCS})
|
||||
#target_link_libraries(tp_web python3.7m ssl crypto dl pthread rt util)
|
||||
target_link_libraries(tp_web python3.7m ssl crypto dl pthread rt util)
|
||||
|
|
BIN
server/www/packages/packages-darwin/x64/PIL/.dylibs/libfreetype.6.dylib
Normal file → Executable file
BIN
server/www/packages/packages-darwin/x64/PIL/.dylibs/libfreetype.6.dylib
Normal file → Executable file
Binary file not shown.
BIN
server/www/packages/packages-darwin/x64/PIL/.dylibs/libjpeg.9.dylib
Normal file → Executable file
BIN
server/www/packages/packages-darwin/x64/PIL/.dylibs/libjpeg.9.dylib
Normal file → Executable file
Binary file not shown.
BIN
server/www/packages/packages-darwin/x64/PIL/.dylibs/liblcms2.2.dylib
Normal file → Executable file
BIN
server/www/packages/packages-darwin/x64/PIL/.dylibs/liblcms2.2.dylib
Normal file → Executable file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
server/www/packages/packages-darwin/x64/PIL/.dylibs/libpng16.16.dylib
Normal file → Executable file
BIN
server/www/packages/packages-darwin/x64/PIL/.dylibs/libpng16.16.dylib
Normal file → Executable file
Binary file not shown.
BIN
server/www/packages/packages-darwin/x64/PIL/.dylibs/libtiff.5.dylib
Normal file → Executable file
BIN
server/www/packages/packages-darwin/x64/PIL/.dylibs/libtiff.5.dylib
Normal file → Executable file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -17,8 +17,9 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from PIL import Image
|
||||
from PIL import FontFile
|
||||
from __future__ import print_function
|
||||
|
||||
from . import Image, FontFile
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
|
@ -119,9 +120,9 @@ class BdfFontFile(FontFile.FontFile):
|
|||
|
||||
# fontname = ";".join(font[1:])
|
||||
|
||||
# print "#", fontname
|
||||
# print("#", fontname)
|
||||
# for i in comments:
|
||||
# print "#", i
|
||||
# print("#", i)
|
||||
|
||||
while True:
|
||||
c = bdf_char(fp)
|
||||
|
|
|
@ -0,0 +1,435 @@
|
|||
"""
|
||||
Blizzard Mipmap Format (.blp)
|
||||
Jerome Leclanche <jerome@leclan.ch>
|
||||
|
||||
The contents of this file are hereby released in the public domain (CC0)
|
||||
Full text of the CC0 license:
|
||||
https://creativecommons.org/publicdomain/zero/1.0/
|
||||
|
||||
BLP1 files, used mostly in Warcraft III, are not fully supported.
|
||||
All types of BLP2 files used in World of Warcraft are supported.
|
||||
|
||||
The BLP file structure consists of a header, up to 16 mipmaps of the
|
||||
texture
|
||||
|
||||
Texture sizes must be powers of two, though the two dimensions do
|
||||
not have to be equal; 512x256 is valid, but 512x200 is not.
|
||||
The first mipmap (mipmap #0) is the full size image; each subsequent
|
||||
mipmap halves both dimensions. The final mipmap should be 1x1.
|
||||
|
||||
BLP files come in many different flavours:
|
||||
* JPEG-compressed (type == 0) - only supported for BLP1.
|
||||
* RAW images (type == 1, encoding == 1). Each mipmap is stored as an
|
||||
array of 8-bit values, one per pixel, left to right, top to bottom.
|
||||
Each value is an index to the palette.
|
||||
* DXT-compressed (type == 1, encoding == 2):
|
||||
- DXT1 compression is used if alpha_encoding == 0.
|
||||
- An additional alpha bit is used if alpha_depth == 1.
|
||||
- DXT3 compression is used if alpha_encoding == 1.
|
||||
- DXT5 compression is used if alpha_encoding == 7.
|
||||
"""
|
||||
|
||||
import struct
|
||||
from io import BytesIO
|
||||
|
||||
from . import Image, ImageFile
|
||||
|
||||
|
||||
BLP_FORMAT_JPEG = 0
|
||||
|
||||
BLP_ENCODING_UNCOMPRESSED = 1
|
||||
BLP_ENCODING_DXT = 2
|
||||
BLP_ENCODING_UNCOMPRESSED_RAW_BGRA = 3
|
||||
|
||||
BLP_ALPHA_ENCODING_DXT1 = 0
|
||||
BLP_ALPHA_ENCODING_DXT3 = 1
|
||||
BLP_ALPHA_ENCODING_DXT5 = 7
|
||||
|
||||
|
||||
def unpack_565(i):
|
||||
return (
|
||||
((i >> 11) & 0x1f) << 3,
|
||||
((i >> 5) & 0x3f) << 2,
|
||||
(i & 0x1f) << 3
|
||||
)
|
||||
|
||||
|
||||
def decode_dxt1(data, alpha=False):
|
||||
"""
|
||||
input: one "row" of data (i.e. will produce 4*width pixels)
|
||||
"""
|
||||
|
||||
blocks = len(data) // 8 # number of blocks in row
|
||||
ret = (bytearray(), bytearray(), bytearray(), bytearray())
|
||||
|
||||
for block in range(blocks):
|
||||
# Decode next 8-byte block.
|
||||
idx = block * 8
|
||||
color0, color1, bits = struct.unpack_from("<HHI", data, idx)
|
||||
|
||||
r0, g0, b0 = unpack_565(color0)
|
||||
r1, g1, b1 = unpack_565(color1)
|
||||
|
||||
# Decode this block into 4x4 pixels
|
||||
# Accumulate the results onto our 4 row accumulators
|
||||
for j in range(4):
|
||||
for i in range(4):
|
||||
# get next control op and generate a pixel
|
||||
|
||||
control = bits & 3
|
||||
bits = bits >> 2
|
||||
|
||||
a = 0xFF
|
||||
if control == 0:
|
||||
r, g, b = r0, g0, b0
|
||||
elif control == 1:
|
||||
r, g, b = r1, g1, b1
|
||||
elif control == 2:
|
||||
if color0 > color1:
|
||||
r = (2 * r0 + r1) // 3
|
||||
g = (2 * g0 + g1) // 3
|
||||
b = (2 * b0 + b1) // 3
|
||||
else:
|
||||
r = (r0 + r1) // 2
|
||||
g = (g0 + g1) // 2
|
||||
b = (b0 + b1) // 2
|
||||
elif control == 3:
|
||||
if color0 > color1:
|
||||
r = (2 * r1 + r0) // 3
|
||||
g = (2 * g1 + g0) // 3
|
||||
b = (2 * b1 + b0) // 3
|
||||
else:
|
||||
r, g, b, a = 0, 0, 0, 0
|
||||
|
||||
if alpha:
|
||||
ret[j].extend([r, g, b, a])
|
||||
else:
|
||||
ret[j].extend([r, g, b])
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def decode_dxt3(data):
|
||||
"""
|
||||
input: one "row" of data (i.e. will produce 4*width pixels)
|
||||
"""
|
||||
|
||||
blocks = len(data) // 16 # number of blocks in row
|
||||
ret = (bytearray(), bytearray(), bytearray(), bytearray())
|
||||
|
||||
for block in range(blocks):
|
||||
idx = block * 16
|
||||
block = data[idx:idx + 16]
|
||||
# Decode next 16-byte block.
|
||||
bits = struct.unpack_from("<8B", block)
|
||||
color0, color1 = struct.unpack_from("<HH", block, 8)
|
||||
|
||||
code, = struct.unpack_from("<I", block, 12)
|
||||
|
||||
r0, g0, b0 = unpack_565(color0)
|
||||
r1, g1, b1 = unpack_565(color1)
|
||||
|
||||
for j in range(4):
|
||||
high = False # Do we want the higher bits?
|
||||
for i in range(4):
|
||||
alphacode_index = (4 * j + i) // 2
|
||||
a = bits[alphacode_index]
|
||||
if high:
|
||||
high = False
|
||||
a >>= 4
|
||||
else:
|
||||
high = True
|
||||
a &= 0xf
|
||||
a *= 17 # We get a value between 0 and 15
|
||||
|
||||
color_code = (code >> 2 * (4 * j + i)) & 0x03
|
||||
|
||||
if color_code == 0:
|
||||
r, g, b = r0, g0, b0
|
||||
elif color_code == 1:
|
||||
r, g, b = r1, g1, b1
|
||||
elif color_code == 2:
|
||||
r = (2 * r0 + r1) // 3
|
||||
g = (2 * g0 + g1) // 3
|
||||
b = (2 * b0 + b1) // 3
|
||||
elif color_code == 3:
|
||||
r = (2 * r1 + r0) // 3
|
||||
g = (2 * g1 + g0) // 3
|
||||
b = (2 * b1 + b0) // 3
|
||||
|
||||
ret[j].extend([r, g, b, a])
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def decode_dxt5(data):
|
||||
"""
|
||||
input: one "row" of data (i.e. will produce 4 * width pixels)
|
||||
"""
|
||||
|
||||
blocks = len(data) // 16 # number of blocks in row
|
||||
ret = (bytearray(), bytearray(), bytearray(), bytearray())
|
||||
|
||||
for block in range(blocks):
|
||||
idx = block * 16
|
||||
block = data[idx:idx + 16]
|
||||
# Decode next 16-byte block.
|
||||
a0, a1 = struct.unpack_from("<BB", block)
|
||||
|
||||
bits = struct.unpack_from("<6B", block, 2)
|
||||
alphacode1 = (
|
||||
bits[2] | (bits[3] << 8) | (bits[4] << 16) | (bits[5] << 24)
|
||||
)
|
||||
alphacode2 = bits[0] | (bits[1] << 8)
|
||||
|
||||
color0, color1 = struct.unpack_from("<HH", block, 8)
|
||||
|
||||
code, = struct.unpack_from("<I", block, 12)
|
||||
|
||||
r0, g0, b0 = unpack_565(color0)
|
||||
r1, g1, b1 = unpack_565(color1)
|
||||
|
||||
for j in range(4):
|
||||
for i in range(4):
|
||||
# get next control op and generate a pixel
|
||||
alphacode_index = 3 * (4 * j + i)
|
||||
|
||||
if alphacode_index <= 12:
|
||||
alphacode = (alphacode2 >> alphacode_index) & 0x07
|
||||
elif alphacode_index == 15:
|
||||
alphacode = (alphacode2 >> 15) | ((alphacode1 << 1) & 0x06)
|
||||
else: # alphacode_index >= 18 and alphacode_index <= 45
|
||||
alphacode = (alphacode1 >> (alphacode_index - 16)) & 0x07
|
||||
|
||||
if alphacode == 0:
|
||||
a = a0
|
||||
elif alphacode == 1:
|
||||
a = a1
|
||||
elif a0 > a1:
|
||||
a = ((8 - alphacode) * a0 + (alphacode - 1) * a1) // 7
|
||||
elif alphacode == 6:
|
||||
a = 0
|
||||
elif alphacode == 7:
|
||||
a = 255
|
||||
else:
|
||||
a = ((6 - alphacode) * a0 + (alphacode - 1) * a1) // 5
|
||||
|
||||
color_code = (code >> 2 * (4 * j + i)) & 0x03
|
||||
|
||||
if color_code == 0:
|
||||
r, g, b = r0, g0, b0
|
||||
elif color_code == 1:
|
||||
r, g, b = r1, g1, b1
|
||||
elif color_code == 2:
|
||||
r = (2 * r0 + r1) // 3
|
||||
g = (2 * g0 + g1) // 3
|
||||
b = (2 * b0 + b1) // 3
|
||||
elif color_code == 3:
|
||||
r = (2 * r1 + r0) // 3
|
||||
g = (2 * g1 + g0) // 3
|
||||
b = (2 * b1 + b0) // 3
|
||||
|
||||
ret[j].extend([r, g, b, a])
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
class BLPFormatError(NotImplementedError):
|
||||
pass
|
||||
|
||||
|
||||
class BlpImageFile(ImageFile.ImageFile):
|
||||
"""
|
||||
Blizzard Mipmap Format
|
||||
"""
|
||||
format = "BLP"
|
||||
format_description = "Blizzard Mipmap Format"
|
||||
|
||||
def _open(self):
|
||||
self.magic = self.fp.read(4)
|
||||
self._read_blp_header()
|
||||
|
||||
if self.magic == b"BLP1":
|
||||
decoder = "BLP1"
|
||||
self.mode = "RGB"
|
||||
elif self.magic == b"BLP2":
|
||||
decoder = "BLP2"
|
||||
self.mode = "RGBA" if self._blp_alpha_depth else "RGB"
|
||||
else:
|
||||
raise BLPFormatError("Bad BLP magic %r" % (self.magic))
|
||||
|
||||
self.tile = [
|
||||
(decoder, (0, 0) + self.size, 0, (self.mode, 0, 1))
|
||||
]
|
||||
|
||||
def _read_blp_header(self):
|
||||
self._blp_compression, = struct.unpack("<i", self.fp.read(4))
|
||||
|
||||
self._blp_encoding, = struct.unpack("<b", self.fp.read(1))
|
||||
self._blp_alpha_depth, = struct.unpack("<b", self.fp.read(1))
|
||||
self._blp_alpha_encoding, = struct.unpack("<b", self.fp.read(1))
|
||||
self._blp_mips, = struct.unpack("<b", self.fp.read(1))
|
||||
|
||||
self.size = struct.unpack("<II", self.fp.read(8))
|
||||
|
||||
if self.magic == b"BLP1":
|
||||
# Only present for BLP1
|
||||
self._blp_encoding, = struct.unpack("<i", self.fp.read(4))
|
||||
self._blp_subtype, = struct.unpack("<i", self.fp.read(4))
|
||||
|
||||
self._blp_offsets = struct.unpack("<16I", self.fp.read(16 * 4))
|
||||
self._blp_lengths = struct.unpack("<16I", self.fp.read(16 * 4))
|
||||
|
||||
|
||||
class _BLPBaseDecoder(ImageFile.PyDecoder):
|
||||
_pulls_fd = True
|
||||
|
||||
def decode(self, buffer):
|
||||
try:
|
||||
self.fd.seek(0)
|
||||
self.magic = self.fd.read(4)
|
||||
self._read_blp_header()
|
||||
self._load()
|
||||
except struct.error:
|
||||
raise IOError("Truncated Blp file")
|
||||
return 0, 0
|
||||
|
||||
def _read_palette(self):
|
||||
ret = []
|
||||
for i in range(256):
|
||||
try:
|
||||
b, g, r, a = struct.unpack("<4B", self.fd.read(4))
|
||||
except struct.error:
|
||||
break
|
||||
ret.append((b, g, r, a))
|
||||
return ret
|
||||
|
||||
def _read_blp_header(self):
|
||||
self._blp_compression, = struct.unpack("<i", self.fd.read(4))
|
||||
|
||||
self._blp_encoding, = struct.unpack("<b", self.fd.read(1))
|
||||
self._blp_alpha_depth, = struct.unpack("<b", self.fd.read(1))
|
||||
self._blp_alpha_encoding, = struct.unpack("<b", self.fd.read(1))
|
||||
self._blp_mips, = struct.unpack("<b", self.fd.read(1))
|
||||
|
||||
self.size = struct.unpack("<II", self.fd.read(8))
|
||||
|
||||
if self.magic == b"BLP1":
|
||||
# Only present for BLP1
|
||||
self._blp_encoding, = struct.unpack("<i", self.fd.read(4))
|
||||
self._blp_subtype, = struct.unpack("<i", self.fd.read(4))
|
||||
|
||||
self._blp_offsets = struct.unpack("<16I", self.fd.read(16 * 4))
|
||||
self._blp_lengths = struct.unpack("<16I", self.fd.read(16 * 4))
|
||||
|
||||
|
||||
class BLP1Decoder(_BLPBaseDecoder):
|
||||
|
||||
def _load(self):
|
||||
if self._blp_compression == BLP_FORMAT_JPEG:
|
||||
self._decode_jpeg_stream()
|
||||
|
||||
elif self._blp_compression == 1:
|
||||
if self._blp_encoding in (4, 5):
|
||||
data = bytearray()
|
||||
palette = self._read_palette()
|
||||
_data = BytesIO(self.fd.read(self._blp_lengths[0]))
|
||||
while True:
|
||||
try:
|
||||
offset, = struct.unpack("<B", _data.read(1))
|
||||
except struct.error:
|
||||
break
|
||||
b, g, r, a = palette[offset]
|
||||
data.extend([r, g, b])
|
||||
|
||||
self.set_as_raw(bytes(data))
|
||||
else:
|
||||
raise BLPFormatError(
|
||||
"Unsupported BLP encoding %r" % (self._blp_encoding)
|
||||
)
|
||||
else:
|
||||
raise BLPFormatError(
|
||||
"Unsupported BLP compression %r" % (self._blp_encoding)
|
||||
)
|
||||
|
||||
def _decode_jpeg_stream(self):
|
||||
from PIL.JpegImagePlugin import JpegImageFile
|
||||
|
||||
jpeg_header_size, = struct.unpack("<I", self.fd.read(4))
|
||||
jpeg_header = self.fd.read(jpeg_header_size)
|
||||
self.fd.read(self._blp_offsets[0] - self.fd.tell()) # What IS this?
|
||||
data = self.fd.read(self._blp_lengths[0])
|
||||
data = jpeg_header + data
|
||||
data = BytesIO(data)
|
||||
image = JpegImageFile(data)
|
||||
self.tile = image.tile # :/
|
||||
self.fd = image.fp
|
||||
self.mode = image.mode
|
||||
|
||||
|
||||
class BLP2Decoder(_BLPBaseDecoder):
|
||||
|
||||
def _load(self):
|
||||
palette = self._read_palette()
|
||||
|
||||
data = bytearray()
|
||||
self.fd.seek(self._blp_offsets[0])
|
||||
|
||||
if self._blp_compression == 1:
|
||||
# Uncompressed or DirectX compression
|
||||
|
||||
if self._blp_encoding == BLP_ENCODING_UNCOMPRESSED:
|
||||
_data = BytesIO(self.fd.read(self._blp_lengths[0]))
|
||||
while True:
|
||||
try:
|
||||
offset, = struct.unpack("<B", _data.read(1))
|
||||
except struct.error:
|
||||
break
|
||||
b, g, r, a = palette[offset]
|
||||
data.extend((r, g, b))
|
||||
|
||||
elif self._blp_encoding == BLP_ENCODING_DXT:
|
||||
if self._blp_alpha_encoding == BLP_ALPHA_ENCODING_DXT1:
|
||||
linesize = (self.size[0] + 3) // 4 * 8
|
||||
for yb in range((self.size[1] + 3) // 4):
|
||||
for d in decode_dxt1(
|
||||
self.fd.read(linesize),
|
||||
alpha=bool(self._blp_alpha_depth)
|
||||
):
|
||||
data += d
|
||||
|
||||
elif self._blp_alpha_encoding == BLP_ALPHA_ENCODING_DXT3:
|
||||
linesize = (self.size[0] + 3) // 4 * 16
|
||||
for yb in range((self.size[1] + 3) // 4):
|
||||
for d in decode_dxt3(self.fd.read(linesize)):
|
||||
data += d
|
||||
|
||||
elif self._blp_alpha_encoding == BLP_ALPHA_ENCODING_DXT5:
|
||||
linesize = (self.size[0] + 3) // 4 * 16
|
||||
for yb in range((self.size[1] + 3) // 4):
|
||||
for d in decode_dxt5(self.fd.read(linesize)):
|
||||
data += d
|
||||
else:
|
||||
raise BLPFormatError("Unsupported alpha encoding %r" % (
|
||||
self._blp_alpha_encoding
|
||||
))
|
||||
else:
|
||||
raise BLPFormatError(
|
||||
"Unknown BLP encoding %r" % (self._blp_encoding)
|
||||
)
|
||||
|
||||
else:
|
||||
raise BLPFormatError(
|
||||
"Unknown BLP compression %r" % (self._blp_compression)
|
||||
)
|
||||
|
||||
self.set_as_raw(bytes(data))
|
||||
|
||||
|
||||
Image.register_open(
|
||||
BlpImageFile.format, BlpImageFile, lambda p: p[:4] in (b"BLP1", b"BLP2")
|
||||
)
|
||||
Image.register_extension(BlpImageFile.format, ".blp")
|
||||
|
||||
Image.register_decoder("BLP1", BLP1Decoder)
|
||||
Image.register_decoder("BLP2", BLP2Decoder)
|
|
@ -24,18 +24,13 @@
|
|||
#
|
||||
|
||||
|
||||
from PIL import Image, ImageFile, ImagePalette, _binary
|
||||
from . import Image, ImageFile, ImagePalette
|
||||
from ._binary import i8, i16le as i16, i32le as i32, \
|
||||
o8, o16le as o16, o32le as o32
|
||||
import math
|
||||
|
||||
__version__ = "0.7"
|
||||
|
||||
i8 = _binary.i8
|
||||
i16 = _binary.i16le
|
||||
i32 = _binary.i32le
|
||||
o8 = _binary.o8
|
||||
o16 = _binary.o16le
|
||||
o32 = _binary.o32le
|
||||
|
||||
#
|
||||
# --------------------------------------------------------------------
|
||||
# Read BMP file
|
||||
|
@ -73,7 +68,7 @@ class BmpImageFile(ImageFile.ImageFile):
|
|||
read, seek = self.fp.read, self.fp.seek
|
||||
if header:
|
||||
seek(header)
|
||||
file_info = dict()
|
||||
file_info = {}
|
||||
file_info['header_size'] = i32(read(4)) # read bmp header size @offset 14 (this is part of the header size)
|
||||
file_info['direction'] = -1
|
||||
# --------------------- If requested, read header at a specific position
|
||||
|
@ -109,7 +104,13 @@ class BmpImageFile(ImageFile.ImageFile):
|
|||
for idx, mask in enumerate(['r_mask', 'g_mask', 'b_mask', 'a_mask']):
|
||||
file_info[mask] = i32(header_data[36+idx*4:40+idx*4])
|
||||
else:
|
||||
for mask in ['r_mask', 'g_mask', 'b_mask', 'a_mask']:
|
||||
# 40 byte headers only have the three components in the bitfields masks,
|
||||
# ref: https://msdn.microsoft.com/en-us/library/windows/desktop/dd183376(v=vs.85).aspx
|
||||
# See also https://github.com/python-pillow/Pillow/issues/1293
|
||||
# There is a 4th component in the RGBQuad, in the alpha location, but it
|
||||
# is listed as a reserved component, and it is not generally an alpha channel
|
||||
file_info['a_mask'] = 0x0
|
||||
for mask in ['r_mask', 'g_mask', 'b_mask']:
|
||||
file_info[mask] = i32(read(4))
|
||||
file_info['rgb_mask'] = (file_info['r_mask'], file_info['g_mask'], file_info['b_mask'])
|
||||
file_info['rgba_mask'] = (file_info['r_mask'], file_info['g_mask'], file_info['b_mask'], file_info['a_mask'])
|
||||
|
@ -130,12 +131,13 @@ class BmpImageFile(ImageFile.ImageFile):
|
|||
# ----------------- Process BMP with Bitfields compression (not palette)
|
||||
if file_info['compression'] == self.BITFIELDS:
|
||||
SUPPORTED = {
|
||||
32: [(0xff0000, 0xff00, 0xff, 0x0), (0xff0000, 0xff00, 0xff, 0xff000000), (0x0, 0x0, 0x0, 0x0)],
|
||||
32: [(0xff0000, 0xff00, 0xff, 0x0), (0xff0000, 0xff00, 0xff, 0xff000000), (0x0, 0x0, 0x0, 0x0), (0xff000000, 0xff0000, 0xff00, 0x0)],
|
||||
24: [(0xff0000, 0xff00, 0xff)],
|
||||
16: [(0xf800, 0x7e0, 0x1f), (0x7c00, 0x3e0, 0x1f)]
|
||||
}
|
||||
MASK_MODES = {
|
||||
(32, (0xff0000, 0xff00, 0xff, 0x0)): "BGRX",
|
||||
(32, (0xff000000, 0xff0000, 0xff00, 0x0)): "XBGR",
|
||||
(32, (0xff0000, 0xff00, 0xff, 0xff000000)): "BGRA",
|
||||
(32, (0x0, 0x0, 0x0, 0x0)): "BGRA",
|
||||
(24, (0xff0000, 0xff00, 0xff)): "BGR",
|
||||
|
@ -214,6 +216,7 @@ class DibImageFile(BmpImageFile):
|
|||
# --------------------------------------------------------------------
|
||||
# Write BMP file
|
||||
|
||||
|
||||
SAVE = {
|
||||
"1": ("1", 1, 2),
|
||||
"L": ("L", 8, 256),
|
||||
|
@ -223,15 +226,12 @@ SAVE = {
|
|||
}
|
||||
|
||||
|
||||
def _save(im, fp, filename, check=0):
|
||||
def _save(im, fp, filename):
|
||||
try:
|
||||
rawmode, bits, colors = SAVE[im.mode]
|
||||
except KeyError:
|
||||
raise IOError("cannot write mode %s as BMP" % im.mode)
|
||||
|
||||
if check:
|
||||
return check
|
||||
|
||||
info = im.encoderinfo
|
||||
|
||||
dpi = info.get("dpi", (96, 96))
|
||||
|
@ -280,6 +280,7 @@ def _save(im, fp, filename, check=0):
|
|||
# --------------------------------------------------------------------
|
||||
# Registry
|
||||
|
||||
|
||||
Image.register_open(BmpImageFile.format, BmpImageFile, _accept)
|
||||
Image.register_save(BmpImageFile.format, _save)
|
||||
|
||||
|
|
|
@ -9,17 +9,17 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from PIL import Image, ImageFile
|
||||
from . import Image, ImageFile
|
||||
|
||||
_handler = None
|
||||
|
||||
|
||||
##
|
||||
# Install application-specific BUFR image handler.
|
||||
#
|
||||
# @param handler Handler object.
|
||||
|
||||
def register_handler(handler):
|
||||
"""
|
||||
Install application-specific BUFR image handler.
|
||||
|
||||
:param handler: Handler object.
|
||||
"""
|
||||
global _handler
|
||||
_handler = handler
|
||||
|
||||
|
@ -40,7 +40,7 @@ class BufrStubImageFile(ImageFile.StubImageFile):
|
|||
|
||||
offset = self.fp.tell()
|
||||
|
||||
if not _accept(self.fp.read(8)):
|
||||
if not _accept(self.fp.read(4)):
|
||||
raise SyntaxError("Not a BUFR file")
|
||||
|
||||
self.fp.seek(offset)
|
||||
|
|
|
@ -21,14 +21,14 @@
|
|||
|
||||
class ContainerIO(object):
|
||||
|
||||
##
|
||||
# Create file object.
|
||||
#
|
||||
# @param file Existing file.
|
||||
# @param offset Start of region, in bytes.
|
||||
# @param length Size of region, in bytes.
|
||||
|
||||
def __init__(self, file, offset, length):
|
||||
"""
|
||||
Create file object.
|
||||
|
||||
:param file: Existing file.
|
||||
:param offset: Start of region, in bytes.
|
||||
:param length: Size of region, in bytes.
|
||||
"""
|
||||
self.fh = file
|
||||
self.pos = 0
|
||||
self.offset = offset
|
||||
|
@ -41,15 +41,15 @@ class ContainerIO(object):
|
|||
def isatty(self):
|
||||
return 0
|
||||
|
||||
##
|
||||
# Move file pointer.
|
||||
#
|
||||
# @param offset Offset in bytes.
|
||||
# @param mode Starting position. Use 0 for beginning of region, 1
|
||||
# for current offset, and 2 for end of region. You cannot move
|
||||
# the pointer outside the defined region.
|
||||
|
||||
def seek(self, offset, mode=0):
|
||||
"""
|
||||
Move file pointer.
|
||||
|
||||
:param offset: Offset in bytes.
|
||||
:param mode: Starting position. Use 0 for beginning of region, 1
|
||||
for current offset, and 2 for end of region. You cannot move
|
||||
the pointer outside the defined region.
|
||||
"""
|
||||
if mode == 1:
|
||||
self.pos = self.pos + offset
|
||||
elif mode == 2:
|
||||
|
@ -60,23 +60,22 @@ class ContainerIO(object):
|
|||
self.pos = max(0, min(self.pos, self.length))
|
||||
self.fh.seek(self.offset + self.pos)
|
||||
|
||||
##
|
||||
# Get current file pointer.
|
||||
#
|
||||
# @return Offset from start of region, in bytes.
|
||||
|
||||
def tell(self):
|
||||
"""
|
||||
Get current file pointer.
|
||||
|
||||
:returns: Offset from start of region, in bytes.
|
||||
"""
|
||||
return self.pos
|
||||
|
||||
##
|
||||
# Read data.
|
||||
#
|
||||
# @def read(bytes=0)
|
||||
# @param bytes Number of bytes to read. If omitted or zero,
|
||||
# read until end of region.
|
||||
# @return An 8-bit string.
|
||||
|
||||
def read(self, n=0):
|
||||
"""
|
||||
Read data.
|
||||
|
||||
:param n: Number of bytes to read. If omitted or zero,
|
||||
read until end of region.
|
||||
:returns: An 8-bit string.
|
||||
"""
|
||||
if n:
|
||||
n = min(n, self.length - self.pos)
|
||||
else:
|
||||
|
@ -86,12 +85,12 @@ class ContainerIO(object):
|
|||
self.pos = self.pos + n
|
||||
return self.fh.read(n)
|
||||
|
||||
##
|
||||
# Read a line of text.
|
||||
#
|
||||
# @return An 8-bit string.
|
||||
|
||||
def readline(self):
|
||||
"""
|
||||
Read a line of text.
|
||||
|
||||
:returns: An 8-bit string.
|
||||
"""
|
||||
s = ""
|
||||
while True:
|
||||
c = self.read(1)
|
||||
|
@ -102,12 +101,12 @@ class ContainerIO(object):
|
|||
break
|
||||
return s
|
||||
|
||||
##
|
||||
# Read multiple lines of text.
|
||||
#
|
||||
# @return A list of 8-bit strings.
|
||||
|
||||
def readlines(self):
|
||||
"""
|
||||
Read multiple lines of text.
|
||||
|
||||
:returns: A list of 8-bit strings.
|
||||
"""
|
||||
l = []
|
||||
while True:
|
||||
s = self.readline()
|
||||
|
|
|
@ -16,18 +16,16 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
from PIL import Image, BmpImagePlugin, _binary
|
||||
from . import Image, BmpImagePlugin
|
||||
from ._binary import i8, i16le as i16, i32le as i32
|
||||
|
||||
__version__ = "0.1"
|
||||
|
||||
#
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
i8 = _binary.i8
|
||||
i16 = _binary.i16le
|
||||
i32 = _binary.i32le
|
||||
|
||||
|
||||
def _accept(prefix):
|
||||
return prefix[:4] == b"\0\0\2\0"
|
||||
|
@ -58,14 +56,14 @@ class CurImageFile(BmpImagePlugin.BmpImageFile):
|
|||
m = s
|
||||
elif i8(s[0]) > i8(m[0]) and i8(s[1]) > i8(m[1]):
|
||||
m = s
|
||||
# print "width", i8(s[0])
|
||||
# print "height", i8(s[1])
|
||||
# print "colors", i8(s[2])
|
||||
# print "reserved", i8(s[3])
|
||||
# print "hotspot x", i16(s[4:])
|
||||
# print "hotspot y", i16(s[6:])
|
||||
# print "bytes", i32(s[8:])
|
||||
# print "offset", i32(s[12:])
|
||||
# print("width", i8(s[0]))
|
||||
# print("height", i8(s[1]))
|
||||
# print("colors", i8(s[2]))
|
||||
# print("reserved", i8(s[3]))
|
||||
# print("hotspot x", i16(s[4:]))
|
||||
# print("hotspot y", i16(s[6:]))
|
||||
# print("bytes", i32(s[8:]))
|
||||
# print("offset", i32(s[12:]))
|
||||
if not m:
|
||||
raise TypeError("No cursors were found")
|
||||
|
||||
|
|
|
@ -21,15 +21,14 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from PIL import Image, _binary
|
||||
from PIL.PcxImagePlugin import PcxImageFile
|
||||
from . import Image
|
||||
from ._binary import i32le as i32
|
||||
from .PcxImagePlugin import PcxImageFile
|
||||
|
||||
__version__ = "0.2"
|
||||
|
||||
MAGIC = 0x3ADE68B1 # QUIZ: what's this value, then?
|
||||
|
||||
i32 = _binary.i32le
|
||||
|
||||
|
||||
def _accept(prefix):
|
||||
return len(prefix) >= 4 and i32(prefix) == MAGIC
|
||||
|
@ -42,6 +41,7 @@ class DcxImageFile(PcxImageFile):
|
|||
|
||||
format = "DCX"
|
||||
format_description = "Intel DCX"
|
||||
_close_exclusive_fp_after_loading = False
|
||||
|
||||
def _open(self):
|
||||
|
||||
|
@ -59,6 +59,7 @@ class DcxImageFile(PcxImageFile):
|
|||
self._offset.append(offset)
|
||||
|
||||
self.__fp = self.fp
|
||||
self.frame = None
|
||||
self.seek(0)
|
||||
|
||||
@property
|
||||
|
@ -70,8 +71,8 @@ class DcxImageFile(PcxImageFile):
|
|||
return len(self._offset) > 1
|
||||
|
||||
def seek(self, frame):
|
||||
if frame >= len(self._offset):
|
||||
raise EOFError("attempt to seek outside DCX directory")
|
||||
if not self._seek_check(frame):
|
||||
return
|
||||
self.frame = frame
|
||||
self.fp = self.__fp
|
||||
self.fp.seek(self._offset[frame])
|
||||
|
|
|
@ -0,0 +1,172 @@
|
|||
"""
|
||||
A Pillow loader for .dds files (S3TC-compressed aka DXTC)
|
||||
Jerome Leclanche <jerome@leclan.ch>
|
||||
|
||||
Documentation:
|
||||
https://web.archive.org/web/20170802060935/http://oss.sgi.com/projects/ogl-sample/registry/EXT/texture_compression_s3tc.txt
|
||||
|
||||
The contents of this file are hereby released in the public domain (CC0)
|
||||
Full text of the CC0 license:
|
||||
https://creativecommons.org/publicdomain/zero/1.0/
|
||||
"""
|
||||
|
||||
import struct
|
||||
from io import BytesIO
|
||||
from . import Image, ImageFile
|
||||
|
||||
|
||||
# Magic ("DDS ")
|
||||
DDS_MAGIC = 0x20534444
|
||||
|
||||
# DDS flags
|
||||
DDSD_CAPS = 0x1
|
||||
DDSD_HEIGHT = 0x2
|
||||
DDSD_WIDTH = 0x4
|
||||
DDSD_PITCH = 0x8
|
||||
DDSD_PIXELFORMAT = 0x1000
|
||||
DDSD_MIPMAPCOUNT = 0x20000
|
||||
DDSD_LINEARSIZE = 0x80000
|
||||
DDSD_DEPTH = 0x800000
|
||||
|
||||
# DDS caps
|
||||
DDSCAPS_COMPLEX = 0x8
|
||||
DDSCAPS_TEXTURE = 0x1000
|
||||
DDSCAPS_MIPMAP = 0x400000
|
||||
|
||||
DDSCAPS2_CUBEMAP = 0x200
|
||||
DDSCAPS2_CUBEMAP_POSITIVEX = 0x400
|
||||
DDSCAPS2_CUBEMAP_NEGATIVEX = 0x800
|
||||
DDSCAPS2_CUBEMAP_POSITIVEY = 0x1000
|
||||
DDSCAPS2_CUBEMAP_NEGATIVEY = 0x2000
|
||||
DDSCAPS2_CUBEMAP_POSITIVEZ = 0x4000
|
||||
DDSCAPS2_CUBEMAP_NEGATIVEZ = 0x8000
|
||||
DDSCAPS2_VOLUME = 0x200000
|
||||
|
||||
# Pixel Format
|
||||
DDPF_ALPHAPIXELS = 0x1
|
||||
DDPF_ALPHA = 0x2
|
||||
DDPF_FOURCC = 0x4
|
||||
DDPF_PALETTEINDEXED8 = 0x20
|
||||
DDPF_RGB = 0x40
|
||||
DDPF_LUMINANCE = 0x20000
|
||||
|
||||
|
||||
# dds.h
|
||||
|
||||
DDS_FOURCC = DDPF_FOURCC
|
||||
DDS_RGB = DDPF_RGB
|
||||
DDS_RGBA = DDPF_RGB | DDPF_ALPHAPIXELS
|
||||
DDS_LUMINANCE = DDPF_LUMINANCE
|
||||
DDS_LUMINANCEA = DDPF_LUMINANCE | DDPF_ALPHAPIXELS
|
||||
DDS_ALPHA = DDPF_ALPHA
|
||||
DDS_PAL8 = DDPF_PALETTEINDEXED8
|
||||
|
||||
DDS_HEADER_FLAGS_TEXTURE = (DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH |
|
||||
DDSD_PIXELFORMAT)
|
||||
DDS_HEADER_FLAGS_MIPMAP = DDSD_MIPMAPCOUNT
|
||||
DDS_HEADER_FLAGS_VOLUME = DDSD_DEPTH
|
||||
DDS_HEADER_FLAGS_PITCH = DDSD_PITCH
|
||||
DDS_HEADER_FLAGS_LINEARSIZE = DDSD_LINEARSIZE
|
||||
|
||||
DDS_HEIGHT = DDSD_HEIGHT
|
||||
DDS_WIDTH = DDSD_WIDTH
|
||||
|
||||
DDS_SURFACE_FLAGS_TEXTURE = DDSCAPS_TEXTURE
|
||||
DDS_SURFACE_FLAGS_MIPMAP = DDSCAPS_COMPLEX | DDSCAPS_MIPMAP
|
||||
DDS_SURFACE_FLAGS_CUBEMAP = DDSCAPS_COMPLEX
|
||||
|
||||
DDS_CUBEMAP_POSITIVEX = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_POSITIVEX
|
||||
DDS_CUBEMAP_NEGATIVEX = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_NEGATIVEX
|
||||
DDS_CUBEMAP_POSITIVEY = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_POSITIVEY
|
||||
DDS_CUBEMAP_NEGATIVEY = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_NEGATIVEY
|
||||
DDS_CUBEMAP_POSITIVEZ = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_POSITIVEZ
|
||||
DDS_CUBEMAP_NEGATIVEZ = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_NEGATIVEZ
|
||||
|
||||
|
||||
# DXT1
|
||||
DXT1_FOURCC = 0x31545844
|
||||
|
||||
# DXT3
|
||||
DXT3_FOURCC = 0x33545844
|
||||
|
||||
# DXT5
|
||||
DXT5_FOURCC = 0x35545844
|
||||
|
||||
|
||||
# dxgiformat.h
|
||||
|
||||
DXGI_FORMAT_BC7_TYPELESS = 97
|
||||
DXGI_FORMAT_BC7_UNORM = 98
|
||||
DXGI_FORMAT_BC7_UNORM_SRGB = 99
|
||||
|
||||
|
||||
class DdsImageFile(ImageFile.ImageFile):
|
||||
format = "DDS"
|
||||
format_description = "DirectDraw Surface"
|
||||
|
||||
def _open(self):
|
||||
magic, header_size = struct.unpack("<II", self.fp.read(8))
|
||||
if header_size != 124:
|
||||
raise IOError("Unsupported header size %r" % (header_size))
|
||||
header_bytes = self.fp.read(header_size - 4)
|
||||
if len(header_bytes) != 120:
|
||||
raise IOError("Incomplete header: %s bytes" % len(header_bytes))
|
||||
header = BytesIO(header_bytes)
|
||||
|
||||
flags, height, width = struct.unpack("<3I", header.read(12))
|
||||
self.size = (width, height)
|
||||
self.mode = "RGBA"
|
||||
|
||||
pitch, depth, mipmaps = struct.unpack("<3I", header.read(12))
|
||||
reserved = struct.unpack("<11I", header.read(44))
|
||||
|
||||
# pixel format
|
||||
pfsize, pfflags = struct.unpack("<2I", header.read(8))
|
||||
fourcc = header.read(4)
|
||||
bitcount, rmask, gmask, bmask, amask = struct.unpack("<5I",
|
||||
header.read(20))
|
||||
|
||||
data_start = header_size + 4
|
||||
n = 0
|
||||
if fourcc == b"DXT1":
|
||||
self.pixel_format = "DXT1"
|
||||
n = 1
|
||||
elif fourcc == b"DXT3":
|
||||
self.pixel_format = "DXT3"
|
||||
n = 2
|
||||
elif fourcc == b"DXT5":
|
||||
self.pixel_format = "DXT5"
|
||||
n = 3
|
||||
elif fourcc == b"DX10":
|
||||
data_start += 20
|
||||
# ignoring flags which pertain to volume textures and cubemaps
|
||||
dxt10 = BytesIO(self.fp.read(20))
|
||||
dxgi_format, dimension = struct.unpack("<II", dxt10.read(8))
|
||||
if dxgi_format in (DXGI_FORMAT_BC7_TYPELESS, DXGI_FORMAT_BC7_UNORM):
|
||||
self.pixel_format = "BC7"
|
||||
n = 7
|
||||
elif dxgi_format == DXGI_FORMAT_BC7_UNORM_SRGB:
|
||||
self.pixel_format = "BC7"
|
||||
self.im_info["gamma"] = 1/2.2
|
||||
n = 7
|
||||
else:
|
||||
raise NotImplementedError("Unimplemented DXGI format %d" %
|
||||
(dxgi_format))
|
||||
else:
|
||||
raise NotImplementedError("Unimplemented pixel format %r" %
|
||||
(fourcc))
|
||||
|
||||
self.tile = [
|
||||
("bcn", (0, 0) + self.size, data_start, (n))
|
||||
]
|
||||
|
||||
def load_seek(self, pos):
|
||||
pass
|
||||
|
||||
|
||||
def _validate(prefix):
|
||||
return prefix[:4] == b"DDS "
|
||||
|
||||
|
||||
Image.register_open(DdsImageFile.format, DdsImageFile, _validate)
|
||||
Image.register_extension(DdsImageFile.format, ".dds")
|
|
@ -22,17 +22,17 @@
|
|||
|
||||
import re
|
||||
import io
|
||||
import os
|
||||
import sys
|
||||
from PIL import Image, ImageFile, _binary
|
||||
from . import Image, ImageFile
|
||||
from ._binary import i32le as i32
|
||||
from ._util import py3
|
||||
|
||||
__version__ = "0.5"
|
||||
|
||||
#
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
i32 = _binary.i32le
|
||||
o32 = _binary.o32le
|
||||
|
||||
split = re.compile(r"^%%([^:]*):[ \t]*(.*)[ \t]*$")
|
||||
field = re.compile(r"^%[%!\w]([^:]*)[ \t]*$")
|
||||
|
||||
|
@ -59,8 +59,8 @@ def has_ghostscript():
|
|||
if not sys.platform.startswith('win'):
|
||||
import subprocess
|
||||
try:
|
||||
gs = subprocess.Popen(['gs', '--version'], stdout=subprocess.PIPE)
|
||||
gs.stdout.read()
|
||||
with open(os.devnull, 'wb') as devnull:
|
||||
subprocess.check_call(['gs', '--version'], stdout=devnull)
|
||||
return True
|
||||
except OSError:
|
||||
# no ghostscript
|
||||
|
@ -85,7 +85,6 @@ def Ghostscript(tile, size, fp, scale=1):
|
|||
float((72.0 * size[1]) / (bbox[3]-bbox[1])))
|
||||
# print("Ghostscript", scale, size, orig_size, bbox, orig_bbox, res)
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
|
@ -123,6 +122,7 @@ def Ghostscript(tile, size, fp, scale=1):
|
|||
"-q", # quiet mode
|
||||
"-g%dx%d" % size, # set output geometry (pixels)
|
||||
"-r%fx%f" % res, # set input DPI (dots per inch)
|
||||
"-dBATCH", # exit after processing
|
||||
"-dNOPAUSE", # don't pause between pages,
|
||||
"-dSAFER", # safe mode
|
||||
"-sDEVICE=ppmraw", # ppm driver
|
||||
|
@ -130,6 +130,7 @@ def Ghostscript(tile, size, fp, scale=1):
|
|||
"-c", "%d %d translate" % (-bbox[0], -bbox[1]),
|
||||
# adjust for image origin
|
||||
"-f", infile, # input file
|
||||
"-c", "showpage", # showpage (see: https://bugs.ghostscript.com/show_bug.cgi?id=698272)
|
||||
]
|
||||
|
||||
if gs_windows_binary is not None:
|
||||
|
@ -139,13 +140,10 @@ def Ghostscript(tile, size, fp, scale=1):
|
|||
|
||||
# push data through ghostscript
|
||||
try:
|
||||
gs = subprocess.Popen(command, stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE)
|
||||
gs.stdin.close()
|
||||
status = gs.wait()
|
||||
if status:
|
||||
raise IOError("gs failed (status %d)" % status)
|
||||
im = Image.core.open_ppm(outfile)
|
||||
with open(os.devnull, 'w+b') as devnull:
|
||||
subprocess.check_call(command, stdin=devnull, stdout=devnull)
|
||||
im = Image.open(outfile)
|
||||
im.load()
|
||||
finally:
|
||||
try:
|
||||
os.unlink(outfile)
|
||||
|
@ -154,7 +152,7 @@ def Ghostscript(tile, size, fp, scale=1):
|
|||
except OSError:
|
||||
pass
|
||||
|
||||
return im
|
||||
return im.im.copy()
|
||||
|
||||
|
||||
class PSFile(object):
|
||||
|
@ -201,7 +199,7 @@ class EpsImageFile(ImageFile.ImageFile):
|
|||
format = "EPS"
|
||||
format_description = "Encapsulated Postscript"
|
||||
|
||||
mode_map = {1: "L", 2: "LAB", 3: "RGB"}
|
||||
mode_map = {1: "L", 2: "LAB", 3: "RGB", 4: "CMYK"}
|
||||
|
||||
def _open(self):
|
||||
(length, offset) = self._find_offset(self.fp)
|
||||
|
@ -209,12 +207,12 @@ class EpsImageFile(ImageFile.ImageFile):
|
|||
# Rewrap the open file pointer in something that will
|
||||
# convert line endings and decode to latin-1.
|
||||
try:
|
||||
if bytes is str:
|
||||
# Python2, no encoding conversion necessary
|
||||
fp = open(self.fp.name, "Ur")
|
||||
else:
|
||||
if py3:
|
||||
# Python3, can use bare open command.
|
||||
fp = open(self.fp.name, "Ur", encoding='latin-1')
|
||||
else:
|
||||
# Python2, no encoding conversion necessary
|
||||
fp = open(self.fp.name, "Ur")
|
||||
except:
|
||||
# Expect this for bytesio/stringio
|
||||
fp = PSFile(self.fp)
|
||||
|
@ -230,9 +228,11 @@ class EpsImageFile(ImageFile.ImageFile):
|
|||
#
|
||||
# Load EPS header
|
||||
|
||||
s = fp.readline().strip('\r\n')
|
||||
s_raw = fp.readline()
|
||||
s = s_raw.strip('\r\n')
|
||||
|
||||
while s:
|
||||
while s_raw:
|
||||
if s:
|
||||
if len(s) > 255:
|
||||
raise SyntaxError("not an EPS file")
|
||||
|
||||
|
@ -274,9 +274,10 @@ class EpsImageFile(ImageFile.ImageFile):
|
|||
else:
|
||||
raise IOError("bad EPS header")
|
||||
|
||||
s = fp.readline().strip('\r\n')
|
||||
s_raw = fp.readline()
|
||||
s = s_raw.strip('\r\n')
|
||||
|
||||
if s[:1] != "%":
|
||||
if s and s[:1] != "%":
|
||||
break
|
||||
|
||||
#
|
||||
|
@ -322,7 +323,7 @@ class EpsImageFile(ImageFile.ImageFile):
|
|||
# EPS can contain binary data
|
||||
# or start directly with latin coding
|
||||
# more info see:
|
||||
# http://partners.adobe.com/public/developer/en/ps/5002.EPSF_Spec.pdf
|
||||
# https://web.archive.org/web/20160528181353/http://partners.adobe.com/public/developer/en/ps/5002.EPSF_Spec.pdf
|
||||
offset = i32(s[4:8])
|
||||
length = i32(s[8:12])
|
||||
else:
|
||||
|
@ -379,7 +380,7 @@ def _save(im, fp, filename, eps=1):
|
|||
base_fp = fp
|
||||
if fp != sys.stdout:
|
||||
fp = NoCloseStream(fp)
|
||||
if sys.version_info[0] > 2:
|
||||
if sys.version_info.major > 2:
|
||||
fp = io.TextIOWrapper(fp, encoding='latin-1')
|
||||
|
||||
if eps:
|
||||
|
@ -418,11 +419,11 @@ def _save(im, fp, filename, eps=1):
|
|||
#
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
|
||||
Image.register_open(EpsImageFile.format, EpsImageFile, _accept)
|
||||
|
||||
Image.register_save(EpsImageFile.format, _save)
|
||||
|
||||
Image.register_extension(EpsImageFile.format, ".ps")
|
||||
Image.register_extension(EpsImageFile.format, ".eps")
|
||||
Image.register_extensions(EpsImageFile.format, [".ps", ".eps"])
|
||||
|
||||
Image.register_mime(EpsImageFile.format, "application/postscript")
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
TAGS = {
|
||||
|
||||
# possibly incomplete
|
||||
0x000b: "ProcessingSoftware",
|
||||
0x00fe: "NewSubfileType",
|
||||
0x00ff: "SubfileType",
|
||||
0x0100: "ImageWidth",
|
||||
|
@ -27,12 +28,11 @@ TAGS = {
|
|||
0x0102: "BitsPerSample",
|
||||
0x0103: "Compression",
|
||||
0x0106: "PhotometricInterpretation",
|
||||
0x0107: "Threshholding",
|
||||
0x0107: "Thresholding",
|
||||
0x0108: "CellWidth",
|
||||
0x0109: "CellLenght",
|
||||
0x0109: "CellLength",
|
||||
0x010a: "FillOrder",
|
||||
0x010d: "DocumentName",
|
||||
0x011d: "PageName",
|
||||
0x010e: "ImageDescription",
|
||||
0x010f: "Make",
|
||||
0x0110: "Model",
|
||||
|
@ -40,41 +40,80 @@ TAGS = {
|
|||
0x0112: "Orientation",
|
||||
0x0115: "SamplesPerPixel",
|
||||
0x0116: "RowsPerStrip",
|
||||
0x0117: "StripByteConunts",
|
||||
0x0117: "StripByteCounts",
|
||||
0x0118: "MinSampleValue",
|
||||
0x0119: "MaxSampleValue",
|
||||
0x011a: "XResolution",
|
||||
0x011b: "YResolution",
|
||||
0x011c: "PlanarConfiguration",
|
||||
0x011d: "PageName",
|
||||
0x0120: "FreeOffsets",
|
||||
0x0121: "FreeByteCounts",
|
||||
0x0122: "GrayResponseUnit",
|
||||
0x0123: "GrayResponseCurve",
|
||||
0x0124: "T4Options",
|
||||
0x0125: "T6Options",
|
||||
0x0128: "ResolutionUnit",
|
||||
0x0129: "PageNumber",
|
||||
0x012d: "TransferFunction",
|
||||
0x0131: "Software",
|
||||
0x0132: "DateTime",
|
||||
0x013b: "Artist",
|
||||
0x013c: "HostComputer",
|
||||
0x013d: "Predictor",
|
||||
0x013e: "WhitePoint",
|
||||
0x013f: "PrimaryChromaticities",
|
||||
0x0140: "ColorMap",
|
||||
0x0141: "HalftoneHints",
|
||||
0x0142: "TileWidth",
|
||||
0x0143: "TileLength",
|
||||
0x0144: "TileOffsets",
|
||||
0x0145: "TileByteCounts",
|
||||
0x014a: "SubIFDs",
|
||||
0x014c: "InkSet",
|
||||
0x014d: "InkNames",
|
||||
0x014e: "NumberOfInks",
|
||||
0x0150: "DotRange",
|
||||
0x0151: "TargetPrinter",
|
||||
0x0152: "ExtraSamples",
|
||||
0x0153: "SampleFormat",
|
||||
0x0154: "SMinSampleValue",
|
||||
0x0155: "SMaxSampleValue",
|
||||
0x0156: "TransferRange",
|
||||
0x0157: "ClipPath",
|
||||
0x0158: "XClipPathUnits",
|
||||
0x0159: "YClipPathUnits",
|
||||
0x015a: "Indexed",
|
||||
0x015b: "JPEGTables",
|
||||
0x015f: "OPIProxy",
|
||||
0x0200: "JPEGProc",
|
||||
0x0201: "JpegIFOffset",
|
||||
0x0202: "JpegIFByteCount",
|
||||
0x0203: "JpegRestartInterval",
|
||||
0x0205: "JpegLosslessPredictors",
|
||||
0x0206: "JpegPointTransforms",
|
||||
0x0207: "JpegQTables",
|
||||
0x0208: "JpegDCTables",
|
||||
0x0209: "JpegACTables",
|
||||
0x0211: "YCbCrCoefficients",
|
||||
0x0212: "YCbCrSubSampling",
|
||||
0x0213: "YCbCrPositioning",
|
||||
0x0214: "ReferenceBlackWhite",
|
||||
0x02bc: "XMLPacket",
|
||||
0x1000: "RelatedImageFileFormat",
|
||||
0x1001: "RelatedImageWidth",
|
||||
0x1002: "RelatedImageLength",
|
||||
0x4746: "Rating",
|
||||
0x4749: "RatingPercent",
|
||||
0x800d: "ImageID",
|
||||
0x828d: "CFARepeatPatternDim",
|
||||
0x828e: "CFAPattern",
|
||||
0x828f: "BatteryLevel",
|
||||
0x8298: "Copyright",
|
||||
0x829a: "ExposureTime",
|
||||
0x829d: "FNumber",
|
||||
0x83bb: "IPTCNAA",
|
||||
0x8649: "ImageResources",
|
||||
0x8769: "ExifOffset",
|
||||
0x8773: "InterColorProfile",
|
||||
0x8822: "ExposureProgram",
|
||||
|
@ -114,6 +153,11 @@ TAGS = {
|
|||
0x9290: "SubsecTime",
|
||||
0x9291: "SubsecTimeOriginal",
|
||||
0x9292: "SubsecTimeDigitized",
|
||||
0x9c9b: "XPTitle",
|
||||
0x9c9c: "XPComment",
|
||||
0x9c9d: "XPAuthor",
|
||||
0x9c9e: "XPKeywords",
|
||||
0x9c9f: "XPSubject",
|
||||
0xa000: "FlashPixVersion",
|
||||
0xa001: "ColorSpace",
|
||||
0xa002: "ExifImageWidth",
|
||||
|
@ -151,7 +195,85 @@ TAGS = {
|
|||
0xa434: "LensModel",
|
||||
0xa435: "LensSerialNumber",
|
||||
0xa500: "Gamma",
|
||||
|
||||
0xc4a5: "PrintImageMatching",
|
||||
0xc612: "DNGVersion",
|
||||
0xc613: "DNGBackwardVersion",
|
||||
0xc614: "UniqueCameraModel",
|
||||
0xc615: "LocalizedCameraModel",
|
||||
0xc616: "CFAPlaneColor",
|
||||
0xc617: "CFALayout",
|
||||
0xc618: "LinearizationTable",
|
||||
0xc619: "BlackLevelRepeatDim",
|
||||
0xc61a: "BlackLevel",
|
||||
0xc61b: "BlackLevelDeltaH",
|
||||
0xc61c: "BlackLevelDeltaV",
|
||||
0xc61d: "WhiteLevel",
|
||||
0xc61e: "DefaultScale",
|
||||
0xc61f: "DefaultCropOrigin",
|
||||
0xc620: "DefaultCropSize",
|
||||
0xc621: "ColorMatrix1",
|
||||
0xc622: "ColorMatrix2",
|
||||
0xc623: "CameraCalibration1",
|
||||
0xc624: "CameraCalibration2",
|
||||
0xc625: "ReductionMatrix1",
|
||||
0xc626: "ReductionMatrix2",
|
||||
0xc627: "AnalogBalance",
|
||||
0xc628: "AsShotNeutral",
|
||||
0xc629: "AsShotWhiteXY",
|
||||
0xc62a: "BaselineExposure",
|
||||
0xc62b: "BaselineNoise",
|
||||
0xc62c: "BaselineSharpness",
|
||||
0xc62d: "BayerGreenSplit",
|
||||
0xc62e: "LinearResponseLimit",
|
||||
0xc62f: "CameraSerialNumber",
|
||||
0xc630: "LensInfo",
|
||||
0xc631: "ChromaBlurRadius",
|
||||
0xc632: "AntiAliasStrength",
|
||||
0xc633: "ShadowScale",
|
||||
0xc634: "DNGPrivateData",
|
||||
0xc635: "MakerNoteSafety",
|
||||
0xc65a: "CalibrationIlluminant1",
|
||||
0xc65b: "CalibrationIlluminant2",
|
||||
0xc65c: "BestQualityScale",
|
||||
0xc65d: "RawDataUniqueID",
|
||||
0xc68b: "OriginalRawFileName",
|
||||
0xc68c: "OriginalRawFileData",
|
||||
0xc68d: "ActiveArea",
|
||||
0xc68e: "MaskedAreas",
|
||||
0xc68f: "AsShotICCProfile",
|
||||
0xc690: "AsShotPreProfileMatrix",
|
||||
0xc691: "CurrentICCProfile",
|
||||
0xc692: "CurrentPreProfileMatrix",
|
||||
0xc6bf: "ColorimetricReference",
|
||||
0xc6f3: "CameraCalibrationSignature",
|
||||
0xc6f4: "ProfileCalibrationSignature",
|
||||
0xc6f6: "AsShotProfileName",
|
||||
0xc6f7: "NoiseReductionApplied",
|
||||
0xc6f8: "ProfileName",
|
||||
0xc6f9: "ProfileHueSatMapDims",
|
||||
0xc6fa: "ProfileHueSatMapData1",
|
||||
0xc6fb: "ProfileHueSatMapData2",
|
||||
0xc6fc: "ProfileToneCurve",
|
||||
0xc6fd: "ProfileEmbedPolicy",
|
||||
0xc6fe: "ProfileCopyright",
|
||||
0xc714: "ForwardMatrix1",
|
||||
0xc715: "ForwardMatrix2",
|
||||
0xc716: "PreviewApplicationName",
|
||||
0xc717: "PreviewApplicationVersion",
|
||||
0xc718: "PreviewSettingsName",
|
||||
0xc719: "PreviewSettingsDigest",
|
||||
0xc71a: "PreviewColorSpace",
|
||||
0xc71b: "PreviewDateTime",
|
||||
0xc71c: "RawImageDigest",
|
||||
0xc71d: "OriginalRawFileDigest",
|
||||
0xc71e: "SubTileBlockSize",
|
||||
0xc71f: "RowInterleaveFactor",
|
||||
0xc725: "ProfileLookTableDims",
|
||||
0xc726: "ProfileLookTableData",
|
||||
0xc740: "OpcodeList1",
|
||||
0xc741: "OpcodeList2",
|
||||
0xc74e: "OpcodeList3",
|
||||
0xc761: "NoiseProfile"
|
||||
}
|
||||
|
||||
##
|
||||
|
|
|
@ -9,17 +9,17 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from PIL import Image, ImageFile
|
||||
from . import Image, ImageFile
|
||||
|
||||
_handler = None
|
||||
|
||||
##
|
||||
# Install application-specific FITS image handler.
|
||||
#
|
||||
# @param handler Handler object.
|
||||
|
||||
|
||||
def register_handler(handler):
|
||||
"""
|
||||
Install application-specific FITS image handler.
|
||||
|
||||
:param handler: Handler object.
|
||||
"""
|
||||
global _handler
|
||||
_handler = handler
|
||||
|
||||
|
@ -72,5 +72,4 @@ def _save(im, fp, filename):
|
|||
Image.register_open(FITSStubImageFile.format, FITSStubImageFile, _accept)
|
||||
Image.register_save(FITSStubImageFile.format, _save)
|
||||
|
||||
Image.register_extension(FITSStubImageFile.format, ".fit")
|
||||
Image.register_extension(FITSStubImageFile.format, ".fits")
|
||||
Image.register_extensions(FITSStubImageFile.format, [".fit", ".fits"])
|
||||
|
|
|
@ -16,15 +16,11 @@
|
|||
#
|
||||
|
||||
|
||||
from PIL import Image, ImageFile, ImagePalette, _binary
|
||||
from . import Image, ImageFile, ImagePalette
|
||||
from ._binary import i8, i16le as i16, i32le as i32, o8
|
||||
|
||||
__version__ = "0.2"
|
||||
|
||||
i8 = _binary.i8
|
||||
i16 = _binary.i16le
|
||||
i32 = _binary.i32le
|
||||
o8 = _binary.o8
|
||||
|
||||
|
||||
#
|
||||
# decoder
|
||||
|
@ -41,6 +37,7 @@ class FliImageFile(ImageFile.ImageFile):
|
|||
|
||||
format = "FLI"
|
||||
format_description = "Autodesk FLI/FLC Animation"
|
||||
_close_exclusive_fp_after_loading = False
|
||||
|
||||
def _open(self):
|
||||
|
||||
|
@ -52,6 +49,9 @@ class FliImageFile(ImageFile.ImageFile):
|
|||
s[20:22] == b"\x00\x00"): # reserved
|
||||
raise SyntaxError("not an FLI/FLC file")
|
||||
|
||||
# frames
|
||||
self.__framecount = i16(s[6:8])
|
||||
|
||||
# image characteristics
|
||||
self.mode = "P"
|
||||
self.size = i16(s[8:10]), i16(s[10:12])
|
||||
|
@ -59,7 +59,7 @@ class FliImageFile(ImageFile.ImageFile):
|
|||
# animation speed
|
||||
duration = i32(s[16:20])
|
||||
if magic == 0xAF11:
|
||||
duration = (duration * 1000) / 70
|
||||
duration = (duration * 1000) // 70
|
||||
self.info["duration"] = duration
|
||||
|
||||
# look for palette
|
||||
|
@ -89,8 +89,6 @@ class FliImageFile(ImageFile.ImageFile):
|
|||
self.__frame = -1
|
||||
self.__fp = self.fp
|
||||
self.__rewind = self.fp.tell()
|
||||
self._n_frames = None
|
||||
self._is_animated = None
|
||||
self.seek(0)
|
||||
|
||||
def _palette(self, palette, shift):
|
||||
|
@ -113,43 +111,20 @@ class FliImageFile(ImageFile.ImageFile):
|
|||
|
||||
@property
|
||||
def n_frames(self):
|
||||
if self._n_frames is None:
|
||||
current = self.tell()
|
||||
try:
|
||||
while True:
|
||||
self.seek(self.tell() + 1)
|
||||
except EOFError:
|
||||
self._n_frames = self.tell() + 1
|
||||
self.seek(current)
|
||||
return self._n_frames
|
||||
return self.__framecount
|
||||
|
||||
@property
|
||||
def is_animated(self):
|
||||
if self._is_animated is None:
|
||||
current = self.tell()
|
||||
|
||||
try:
|
||||
self.seek(1)
|
||||
self._is_animated = True
|
||||
except EOFError:
|
||||
self._is_animated = False
|
||||
|
||||
self.seek(current)
|
||||
return self._is_animated
|
||||
return self.__framecount > 1
|
||||
|
||||
def seek(self, frame):
|
||||
if frame == self.__frame:
|
||||
if not self._seek_check(frame):
|
||||
return
|
||||
if frame < self.__frame:
|
||||
self._seek(0)
|
||||
|
||||
last_frame = self.__frame
|
||||
for f in range(self.__frame + 1, frame + 1):
|
||||
try:
|
||||
self._seek(f)
|
||||
except EOFError:
|
||||
self.seek(last_frame)
|
||||
raise EOFError("no more images in FLI file")
|
||||
|
||||
def _seek(self, frame):
|
||||
if frame == 0:
|
||||
|
@ -179,10 +154,10 @@ class FliImageFile(ImageFile.ImageFile):
|
|||
def tell(self):
|
||||
return self.__frame
|
||||
|
||||
|
||||
#
|
||||
# registry
|
||||
|
||||
Image.register_open(FliImageFile.format, FliImageFile, _accept)
|
||||
|
||||
Image.register_extension(FliImageFile.format, ".fli")
|
||||
Image.register_extension(FliImageFile.format, ".flc")
|
||||
Image.register_extensions(FliImageFile.format, [".fli", ".flc"])
|
||||
|
|
|
@ -14,8 +14,10 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import os
|
||||
from PIL import Image, _binary
|
||||
from . import Image, _binary
|
||||
|
||||
WIDTH = 800
|
||||
|
||||
|
@ -88,7 +90,7 @@ class FontFile(object):
|
|||
x = xx
|
||||
s = src[0] + x0, src[1] + y0, src[2] + x0, src[3] + y0
|
||||
self.bitmap.paste(im.crop(src), s)
|
||||
# print chr(i), dst, s
|
||||
# print(chr(i), dst, s)
|
||||
self.metrics[i] = d, dst, s
|
||||
|
||||
def save(self, filename):
|
||||
|
@ -100,7 +102,7 @@ class FontFile(object):
|
|||
self.bitmap.save(os.path.splitext(filename)[0] + ".pbm", "PNG")
|
||||
|
||||
# font metrics
|
||||
fp = open(os.path.splitext(filename)[0] + ".pil", "wb")
|
||||
with open(os.path.splitext(filename)[0] + ".pil", "wb") as fp:
|
||||
fp.write(b"PILfont\n")
|
||||
fp.write((";;;;;;%d;\n" % self.ysize).encode('ascii')) # HACK!!!
|
||||
fp.write(b"DATA\n")
|
||||
|
@ -110,6 +112,3 @@ class FontFile(object):
|
|||
puti16(fp, [0] * 10)
|
||||
else:
|
||||
puti16(fp, m[0] + m[1] + m[2])
|
||||
fp.close()
|
||||
|
||||
# End of file
|
||||
|
|
|
@ -15,13 +15,15 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
from PIL import Image, ImageFile
|
||||
from PIL.OleFileIO import i8, i32, MAGIC, OleFileIO
|
||||
from . import Image, ImageFile
|
||||
from ._binary import i32le as i32, i8
|
||||
|
||||
import olefile
|
||||
|
||||
__version__ = "0.1"
|
||||
|
||||
|
||||
# we map from colour field tuples to (mode, rawmode) descriptors
|
||||
MODES = {
|
||||
# opacity
|
||||
|
@ -42,7 +44,7 @@ MODES = {
|
|||
# --------------------------------------------------------------------
|
||||
|
||||
def _accept(prefix):
|
||||
return prefix[:8] == MAGIC
|
||||
return prefix[:8] == olefile.MAGIC
|
||||
|
||||
|
||||
##
|
||||
|
@ -59,7 +61,7 @@ class FpxImageFile(ImageFile.ImageFile):
|
|||
# to be a FlashPix file
|
||||
|
||||
try:
|
||||
self.ole = OleFileIO(self.fp)
|
||||
self.ole = olefile.OleFileIO(self.fp)
|
||||
except IOError:
|
||||
raise SyntaxError("not an FPX file; invalid OLE file")
|
||||
|
||||
|
@ -112,7 +114,7 @@ class FpxImageFile(ImageFile.ImageFile):
|
|||
if id in prop:
|
||||
self.jpeg[i] = prop[id]
|
||||
|
||||
# print len(self.jpeg), "tables loaded"
|
||||
# print(len(self.jpeg), "tables loaded")
|
||||
|
||||
self._open_subimage(1, self.maxid)
|
||||
|
||||
|
@ -141,7 +143,7 @@ class FpxImageFile(ImageFile.ImageFile):
|
|||
offset = i32(s, 28)
|
||||
length = i32(s, 32)
|
||||
|
||||
# print size, self.mode, self.rawmode
|
||||
# print(size, self.mode, self.rawmode)
|
||||
|
||||
if size != self.size:
|
||||
raise IOError("subimage mismatch")
|
||||
|
@ -216,11 +218,12 @@ class FpxImageFile(ImageFile.ImageFile):
|
|||
self.fp = self.ole.openstream(self.stream[:2] +
|
||||
["Subimage 0000 Data"])
|
||||
|
||||
ImageFile.ImageFile.load(self)
|
||||
return ImageFile.ImageFile.load(self)
|
||||
|
||||
#
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
|
||||
Image.register_open(FpxImageFile.format, FpxImageFile, _accept)
|
||||
|
||||
Image.register_extension(FpxImageFile.format, ".fpx")
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
"""
|
||||
A Pillow loader for .ftc and .ftu files (FTEX)
|
||||
Jerome Leclanche <jerome@leclan.ch>
|
||||
|
||||
The contents of this file are hereby released in the public domain (CC0)
|
||||
Full text of the CC0 license:
|
||||
https://creativecommons.org/publicdomain/zero/1.0/
|
||||
|
||||
Independence War 2: Edge Of Chaos - Texture File Format - 16 October 2001
|
||||
|
||||
The textures used for 3D objects in Independence War 2: Edge Of Chaos are in a
|
||||
packed custom format called FTEX. This file format uses file extensions FTC and FTU.
|
||||
* FTC files are compressed textures (using standard texture compression).
|
||||
* FTU files are not compressed.
|
||||
Texture File Format
|
||||
The FTC and FTU texture files both use the same format. This
|
||||
has the following structure:
|
||||
{header}
|
||||
{format_directory}
|
||||
{data}
|
||||
Where:
|
||||
{header} = { u32:magic, u32:version, u32:width, u32:height, u32:mipmap_count, u32:format_count }
|
||||
|
||||
* The "magic" number is "FTEX".
|
||||
* "width" and "height" are the dimensions of the texture.
|
||||
* "mipmap_count" is the number of mipmaps in the texture.
|
||||
* "format_count" is the number of texture formats (different versions of the same texture) in this file.
|
||||
|
||||
{format_directory} = format_count * { u32:format, u32:where }
|
||||
|
||||
The format value is 0 for DXT1 compressed textures and 1 for 24-bit RGB uncompressed textures.
|
||||
The texture data for a format starts at the position "where" in the file.
|
||||
|
||||
Each set of texture data in the file has the following structure:
|
||||
{data} = format_count * { u32:mipmap_size, mipmap_size * { u8 } }
|
||||
* "mipmap_size" is the number of bytes in that mip level. For compressed textures this is the
|
||||
size of the texture data compressed with DXT1. For 24 bit uncompressed textures, this is 3 * width * height.
|
||||
Following this are the image bytes for that mipmap level.
|
||||
|
||||
Note: All data is stored in little-Endian (Intel) byte order.
|
||||
"""
|
||||
|
||||
import struct
|
||||
from io import BytesIO
|
||||
from . import Image, ImageFile
|
||||
|
||||
|
||||
MAGIC = b"FTEX"
|
||||
FORMAT_DXT1 = 0
|
||||
FORMAT_UNCOMPRESSED = 1
|
||||
|
||||
|
||||
class FtexImageFile(ImageFile.ImageFile):
|
||||
format = "FTEX"
|
||||
format_description = "Texture File Format (IW2:EOC)"
|
||||
|
||||
def _open(self):
|
||||
magic = struct.unpack("<I", self.fp.read(4))
|
||||
version = struct.unpack("<i", self.fp.read(4))
|
||||
self.size = struct.unpack("<2i", self.fp.read(8))
|
||||
mipmap_count, format_count = struct.unpack("<2i", self.fp.read(8))
|
||||
|
||||
self.mode = "RGB"
|
||||
|
||||
# Only support single-format files. I don't know of any multi-format file.
|
||||
assert format_count == 1
|
||||
|
||||
format, where = struct.unpack("<2i", self.fp.read(8))
|
||||
self.fp.seek(where)
|
||||
mipmap_size, = struct.unpack("<i", self.fp.read(4))
|
||||
|
||||
data = self.fp.read(mipmap_size)
|
||||
|
||||
if format == FORMAT_DXT1:
|
||||
self.mode = "RGBA"
|
||||
self.tile = [("bcn", (0, 0) + self.size, 0, (1))]
|
||||
elif format == FORMAT_UNCOMPRESSED:
|
||||
self.tile = [("raw", (0, 0) + self.size, 0, ('RGB', 0, 1))]
|
||||
else:
|
||||
raise ValueError("Invalid texture compression format: %r" % (format))
|
||||
|
||||
self.fp.close()
|
||||
self.fp = BytesIO(data)
|
||||
|
||||
def load_seek(self, pos):
|
||||
pass
|
||||
|
||||
|
||||
def _validate(prefix):
|
||||
return prefix[:4] == MAGIC
|
||||
|
||||
|
||||
Image.register_open(FtexImageFile.format, FtexImageFile, _validate)
|
||||
Image.register_extensions(FtexImageFile.format, [".ftc", ".ftu"])
|
|
@ -1,25 +1,35 @@
|
|||
#
|
||||
# The Python Imaging Library
|
||||
# $Id$
|
||||
#
|
||||
# load a GIMP brush file
|
||||
#
|
||||
# History:
|
||||
# 96-03-14 fl Created
|
||||
# 16-01-08 es Version 2
|
||||
#
|
||||
# Copyright (c) Secret Labs AB 1997.
|
||||
# Copyright (c) Fredrik Lundh 1996.
|
||||
# Copyright (c) Eric Soroos 2016.
|
||||
#
|
||||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
#
|
||||
# See https://github.com/GNOME/gimp/blob/master/devel-docs/gbr.txt for
|
||||
# format documentation.
|
||||
#
|
||||
# This code Interprets version 1 and 2 .gbr files.
|
||||
# Version 1 files are obsolete, and should not be used for new
|
||||
# brushes.
|
||||
# Version 2 files are saved by GIMP v2.8 (at least)
|
||||
# Version 3 files have a format specifier of 18 for 16bit floats in
|
||||
# the color depth field. This is currently unsupported by Pillow.
|
||||
|
||||
from PIL import Image, ImageFile, _binary
|
||||
|
||||
i32 = _binary.i32be
|
||||
from . import Image, ImageFile
|
||||
from ._binary import i32be as i32
|
||||
|
||||
|
||||
def _accept(prefix):
|
||||
return len(prefix) >= 8 and i32(prefix) >= 20 and i32(prefix[4:8]) == 1
|
||||
return len(prefix) >= 8 and i32(prefix[:4]) >= 20 and i32(prefix[4:8]) in (1, 2)
|
||||
|
||||
|
||||
##
|
||||
|
@ -31,41 +41,54 @@ class GbrImageFile(ImageFile.ImageFile):
|
|||
format_description = "GIMP brush file"
|
||||
|
||||
def _open(self):
|
||||
|
||||
header_size = i32(self.fp.read(4))
|
||||
version = i32(self.fp.read(4))
|
||||
if header_size < 20 or version != 1:
|
||||
if header_size < 20:
|
||||
raise SyntaxError("not a GIMP brush")
|
||||
if version not in (1, 2):
|
||||
raise SyntaxError("Unsupported GIMP brush version: %s" % version)
|
||||
|
||||
width = i32(self.fp.read(4))
|
||||
height = i32(self.fp.read(4))
|
||||
color_depth = i32(self.fp.read(4))
|
||||
if width <= 0 or height <= 0 or color_depth != 1:
|
||||
if width <= 0 or height <= 0:
|
||||
raise SyntaxError("not a GIMP brush")
|
||||
if color_depth not in (1, 4):
|
||||
raise SyntaxError("Unsupported GIMP brush color depth: %s" % color_depth)
|
||||
|
||||
comment = self.fp.read(header_size - 20)[:-1]
|
||||
if version == 1:
|
||||
comment_length = header_size-20
|
||||
else:
|
||||
comment_length = header_size-28
|
||||
magic_number = self.fp.read(4)
|
||||
if magic_number != b'GIMP':
|
||||
raise SyntaxError("not a GIMP brush, bad magic number")
|
||||
self.info['spacing'] = i32(self.fp.read(4))
|
||||
|
||||
comment = self.fp.read(comment_length)[:-1]
|
||||
|
||||
if color_depth == 1:
|
||||
self.mode = "L"
|
||||
else:
|
||||
self.mode = 'RGBA'
|
||||
|
||||
self.size = width, height
|
||||
|
||||
self.info["comment"] = comment
|
||||
|
||||
# Since the brush is so small, we read the data immediately
|
||||
self.data = self.fp.read(width * height)
|
||||
# Image might not be small
|
||||
Image._decompression_bomb_check(self.size)
|
||||
|
||||
# Data is an uncompressed block of w * h * bytes/pixel
|
||||
self._data_size = width * height * color_depth
|
||||
|
||||
def load(self):
|
||||
|
||||
if not self.data:
|
||||
return
|
||||
|
||||
# create an image out of the brush data block
|
||||
self.im = Image.core.new(self.mode, self.size)
|
||||
self.im.frombytes(self.data)
|
||||
self.data = b""
|
||||
self.frombytes(self.fp.read(self._data_size))
|
||||
|
||||
#
|
||||
# registry
|
||||
|
||||
Image.register_open(GbrImageFile.format, GbrImageFile, _accept)
|
||||
|
||||
Image.register_open(GbrImageFile.format, GbrImageFile, _accept)
|
||||
Image.register_extension(GbrImageFile.format, ".gbr")
|
||||
|
|
|
@ -23,19 +23,11 @@
|
|||
# purposes only.
|
||||
|
||||
|
||||
from PIL import ImageFile, ImagePalette, _binary
|
||||
from PIL._util import isPath
|
||||
from . import ImageFile, ImagePalette
|
||||
from ._binary import i8, i16be as i16, i32be as i32
|
||||
|
||||
__version__ = "0.1"
|
||||
|
||||
try:
|
||||
import builtins
|
||||
except ImportError:
|
||||
import __builtin__
|
||||
builtins = __builtin__
|
||||
|
||||
i16 = _binary.i16be
|
||||
|
||||
|
||||
##
|
||||
# Image plugin for the GD uncompressed format. Note that this format
|
||||
|
@ -51,42 +43,41 @@ class GdImageFile(ImageFile.ImageFile):
|
|||
def _open(self):
|
||||
|
||||
# Header
|
||||
s = self.fp.read(775)
|
||||
s = self.fp.read(1037)
|
||||
|
||||
if not i16(s[:2]) in [65534, 65535]:
|
||||
raise SyntaxError("Not a valid GD 2.x .gd file")
|
||||
|
||||
self.mode = "L" # FIXME: "P"
|
||||
self.size = i16(s[0:2]), i16(s[2:4])
|
||||
self.size = i16(s[2:4]), i16(s[4:6])
|
||||
|
||||
trueColor = i8(s[6])
|
||||
trueColorOffset = 2 if trueColor else 0
|
||||
|
||||
# transparency index
|
||||
tindex = i16(s[5:7])
|
||||
tindex = i32(s[7+trueColorOffset:7+trueColorOffset+4])
|
||||
if tindex < 256:
|
||||
self.info["transparent"] = tindex
|
||||
self.info["transparency"] = tindex
|
||||
|
||||
self.palette = ImagePalette.raw("RGB", s[7:])
|
||||
self.palette = ImagePalette.raw("XBGR", s[7+trueColorOffset+4:7+trueColorOffset+4+256*4])
|
||||
|
||||
self.tile = [("raw", (0, 0)+self.size, 775, ("L", 0, -1))]
|
||||
self.tile = [("raw", (0, 0)+self.size, 7+trueColorOffset+4+256*4, ("L", 0, 1))]
|
||||
|
||||
|
||||
##
|
||||
# Load texture from a GD image file.
|
||||
#
|
||||
# @param filename GD file name, or an opened file handle.
|
||||
# @param mode Optional mode. In this version, if the mode argument
|
||||
# is given, it must be "r".
|
||||
# @return An image instance.
|
||||
# @exception IOError If the image could not be read.
|
||||
|
||||
def open(fp, mode="r"):
|
||||
"""
|
||||
Load texture from a GD image file.
|
||||
|
||||
:param filename: GD file name, or an opened file handle.
|
||||
:param mode: Optional mode. In this version, if the mode argument
|
||||
is given, it must be "r".
|
||||
:returns: An image instance.
|
||||
:raises IOError: If the image could not be read.
|
||||
"""
|
||||
if mode != "r":
|
||||
raise ValueError("bad mode")
|
||||
|
||||
if isPath(fp):
|
||||
filename = fp
|
||||
fp = builtins.open(fp, "rb")
|
||||
else:
|
||||
filename = ""
|
||||
|
||||
try:
|
||||
return GdImageFile(fp, filename)
|
||||
return GdImageFile(fp)
|
||||
except SyntaxError:
|
||||
raise IOError("cannot identify this image file")
|
||||
|
|
|
@ -24,21 +24,14 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from PIL import Image, ImageFile, ImagePalette, \
|
||||
ImageChops, ImageSequence, _binary
|
||||
from . import Image, ImageFile, ImagePalette, ImageChops, ImageSequence
|
||||
from ._binary import i8, i16le as i16, o8, o16le as o16
|
||||
|
||||
import itertools
|
||||
|
||||
__version__ = "0.9"
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# Helpers
|
||||
|
||||
i8 = _binary.i8
|
||||
i16 = _binary.i16le
|
||||
o8 = _binary.o8
|
||||
o16 = _binary.o16le
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# Identify/read GIF files
|
||||
|
||||
|
@ -54,6 +47,8 @@ class GifImageFile(ImageFile.ImageFile):
|
|||
|
||||
format = "GIF"
|
||||
format_description = "Compuserve GIF"
|
||||
_close_exclusive_fp_after_loading = False
|
||||
|
||||
global_palette = None
|
||||
|
||||
def data(self):
|
||||
|
@ -107,6 +102,9 @@ class GifImageFile(ImageFile.ImageFile):
|
|||
@property
|
||||
def is_animated(self):
|
||||
if self._is_animated is None:
|
||||
if self._n_frames is not None:
|
||||
self._is_animated = self._n_frames != 1
|
||||
else:
|
||||
current = self.tell()
|
||||
|
||||
try:
|
||||
|
@ -119,7 +117,7 @@ class GifImageFile(ImageFile.ImageFile):
|
|||
return self._is_animated
|
||||
|
||||
def seek(self, frame):
|
||||
if frame == self.__frame:
|
||||
if not self._seek_check(frame):
|
||||
return
|
||||
if frame < self.__frame:
|
||||
self._seek(0)
|
||||
|
@ -198,6 +196,11 @@ class GifImageFile(ImageFile.ImageFile):
|
|||
# correct, but it seems to prevent the last
|
||||
# frame from looking odd for some animations
|
||||
self.disposal_method = dispose_bits
|
||||
elif i8(s) == 254:
|
||||
#
|
||||
# comment extension
|
||||
#
|
||||
self.info["comment"] = block
|
||||
elif i8(s) == 255:
|
||||
#
|
||||
# application extension
|
||||
|
@ -257,7 +260,7 @@ class GifImageFile(ImageFile.ImageFile):
|
|||
|
||||
# only dispose the extent in this frame
|
||||
if self.dispose:
|
||||
self.dispose = self.dispose.crop(self.dispose_extent)
|
||||
self.dispose = self._crop(self.dispose, self.dispose_extent)
|
||||
except (AttributeError, KeyError):
|
||||
pass
|
||||
|
||||
|
@ -280,7 +283,7 @@ class GifImageFile(ImageFile.ImageFile):
|
|||
if self._prev_im and self.disposal_method == 1:
|
||||
# we do this by pasting the updated area onto the previous
|
||||
# frame which we then use as the current image content
|
||||
updated = self.im.crop(self.dispose_extent)
|
||||
updated = self._crop(self.im, self.dispose_extent)
|
||||
self._prev_im.paste(updated, self.dispose_extent,
|
||||
updated.convert('RGBA'))
|
||||
self.im = self._prev_im
|
||||
|
@ -289,52 +292,173 @@ class GifImageFile(ImageFile.ImageFile):
|
|||
# --------------------------------------------------------------------
|
||||
# Write GIF files
|
||||
|
||||
try:
|
||||
import _imaging_gif
|
||||
except ImportError:
|
||||
_imaging_gif = None
|
||||
|
||||
RAWMODE = {
|
||||
"1": "L",
|
||||
"L": "L",
|
||||
"P": "P",
|
||||
"P": "P"
|
||||
}
|
||||
|
||||
|
||||
def _convert_mode(im, initial_call=False):
|
||||
# convert on the fly (EXPERIMENTAL -- I'm not sure PIL
|
||||
# should automatically convert images on save...)
|
||||
def _normalize_mode(im, initial_call=False):
|
||||
"""
|
||||
Takes an image (or frame), returns an image in a mode that is appropriate
|
||||
for saving in a Gif.
|
||||
|
||||
It may return the original image, or it may return an image converted to
|
||||
palette or 'L' mode.
|
||||
|
||||
UNDONE: What is the point of mucking with the initial call palette, for
|
||||
an image that shouldn't have a palette, or it would be a mode 'P' and
|
||||
get returned in the RAWMODE clause.
|
||||
|
||||
:param im: Image object
|
||||
:param initial_call: Default false, set to true for a single frame.
|
||||
:returns: Image object
|
||||
"""
|
||||
if im.mode in RAWMODE:
|
||||
im.load()
|
||||
return im
|
||||
if Image.getmodebase(im.mode) == "RGB":
|
||||
if initial_call:
|
||||
palette_size = 256
|
||||
if im.palette:
|
||||
palette_size = len(im.palette.getdata()[1]) // 3
|
||||
return im.convert("P", palette=1, colors=palette_size)
|
||||
return im.convert("P", palette=Image.ADAPTIVE, colors=palette_size)
|
||||
else:
|
||||
return im.convert("P")
|
||||
return im.convert("L")
|
||||
|
||||
|
||||
def _normalize_palette(im, palette, info):
|
||||
"""
|
||||
Normalizes the palette for image.
|
||||
- Sets the palette to the incoming palette, if provided.
|
||||
- Ensures that there's a palette for L mode images
|
||||
- Optimizes the palette if necessary/desired.
|
||||
|
||||
:param im: Image object
|
||||
:param palette: bytes object containing the source palette, or ....
|
||||
:param info: encoderinfo
|
||||
:returns: Image object
|
||||
"""
|
||||
source_palette = None
|
||||
if palette:
|
||||
# a bytes palette
|
||||
if isinstance(palette, (bytes, bytearray, list)):
|
||||
source_palette = bytearray(palette[:768])
|
||||
if isinstance(palette, ImagePalette.ImagePalette):
|
||||
source_palette = bytearray(itertools.chain.from_iterable(
|
||||
zip(palette.palette[:256],
|
||||
palette.palette[256:512],
|
||||
palette.palette[512:768])))
|
||||
|
||||
if im.mode == "P":
|
||||
if not source_palette:
|
||||
source_palette = im.im.getpalette("RGB")[:768]
|
||||
else: # L-mode
|
||||
if not source_palette:
|
||||
source_palette = bytearray(i//3 for i in range(768))
|
||||
im.palette = ImagePalette.ImagePalette("RGB",
|
||||
palette=source_palette)
|
||||
|
||||
used_palette_colors = _get_optimize(im, info)
|
||||
if used_palette_colors is not None:
|
||||
return im.remap_palette(used_palette_colors, source_palette)
|
||||
|
||||
im.palette.palette = source_palette
|
||||
return im
|
||||
|
||||
|
||||
def _write_single_frame(im, fp, palette):
|
||||
im_out = _normalize_mode(im, True)
|
||||
im_out = _normalize_palette(im_out, palette, im.encoderinfo)
|
||||
|
||||
for s in _get_global_header(im_out, im.encoderinfo):
|
||||
fp.write(s)
|
||||
|
||||
# local image header
|
||||
flags = 0
|
||||
if get_interlace(im):
|
||||
flags = flags | 64
|
||||
_write_local_header(fp, im, (0, 0), flags)
|
||||
|
||||
im_out.encoderconfig = (8, get_interlace(im))
|
||||
ImageFile._save(im_out, fp, [("gif", (0, 0)+im.size, 0,
|
||||
RAWMODE[im_out.mode])])
|
||||
|
||||
fp.write(b"\0") # end of image data
|
||||
|
||||
|
||||
def _write_multiple_frames(im, fp, palette):
|
||||
|
||||
duration = im.encoderinfo.get("duration", None)
|
||||
disposal = im.encoderinfo.get('disposal', None)
|
||||
|
||||
im_frames = []
|
||||
frame_count = 0
|
||||
for imSequence in itertools.chain([im], im.encoderinfo.get("append_images", [])):
|
||||
for im_frame in ImageSequence.Iterator(imSequence):
|
||||
# a copy is required here since seek can still mutate the image
|
||||
im_frame = _normalize_mode(im_frame.copy())
|
||||
im_frame = _normalize_palette(im_frame, palette, im.encoderinfo)
|
||||
|
||||
encoderinfo = im.encoderinfo.copy()
|
||||
if isinstance(duration, (list, tuple)):
|
||||
encoderinfo['duration'] = duration[frame_count]
|
||||
if isinstance(disposal, (list, tuple)):
|
||||
encoderinfo["disposal"] = disposal[frame_count]
|
||||
frame_count += 1
|
||||
|
||||
if im_frames:
|
||||
# delta frame
|
||||
previous = im_frames[-1]
|
||||
if _get_palette_bytes(im_frame) == _get_palette_bytes(previous['im']):
|
||||
delta = ImageChops.subtract_modulo(im_frame,
|
||||
previous['im'])
|
||||
else:
|
||||
delta = ImageChops.subtract_modulo(im_frame.convert('RGB'),
|
||||
previous['im'].convert('RGB'))
|
||||
bbox = delta.getbbox()
|
||||
if not bbox:
|
||||
# This frame is identical to the previous frame
|
||||
if duration:
|
||||
previous['encoderinfo']['duration'] += encoderinfo['duration']
|
||||
continue
|
||||
else:
|
||||
bbox = None
|
||||
im_frames.append({
|
||||
'im': im_frame,
|
||||
'bbox': bbox,
|
||||
'encoderinfo': encoderinfo
|
||||
})
|
||||
|
||||
if len(im_frames) > 1:
|
||||
for frame_data in im_frames:
|
||||
im_frame = frame_data['im']
|
||||
if not frame_data['bbox']:
|
||||
# global header
|
||||
for s in _get_global_header(im_frame,
|
||||
frame_data['encoderinfo']):
|
||||
fp.write(s)
|
||||
offset = (0, 0)
|
||||
else:
|
||||
# compress difference
|
||||
frame_data['encoderinfo']['include_color_table'] = True
|
||||
|
||||
im_frame = im_frame.crop(frame_data['bbox'])
|
||||
offset = frame_data['bbox'][:2]
|
||||
_write_frame_data(fp, im_frame, offset, frame_data['encoderinfo'])
|
||||
return True
|
||||
|
||||
|
||||
def _save_all(im, fp, filename):
|
||||
_save(im, fp, filename, save_all=True)
|
||||
|
||||
|
||||
def _save(im, fp, filename, save_all=False):
|
||||
|
||||
im.encoderinfo.update(im.info)
|
||||
if _imaging_gif:
|
||||
# call external driver
|
||||
try:
|
||||
_imaging_gif.save(im, fp, filename)
|
||||
return
|
||||
except IOError:
|
||||
pass # write uncompressed file
|
||||
|
||||
if im.mode in RAWMODE:
|
||||
im_out = im.copy()
|
||||
else:
|
||||
im_out = _convert_mode(im, True)
|
||||
|
||||
for k, v in im.info.items():
|
||||
im.encoderinfo.setdefault(k, v)
|
||||
# header
|
||||
try:
|
||||
palette = im.encoderinfo["palette"]
|
||||
|
@ -342,58 +466,8 @@ def _save(im, fp, filename, save_all=False):
|
|||
palette = None
|
||||
im.encoderinfo["optimize"] = im.encoderinfo.get("optimize", True)
|
||||
|
||||
if save_all:
|
||||
previous = None
|
||||
|
||||
first_frame = None
|
||||
for im_frame in ImageSequence.Iterator(im):
|
||||
im_frame = _convert_mode(im_frame)
|
||||
|
||||
# To specify duration, add the time in milliseconds to getdata(),
|
||||
# e.g. getdata(im_frame, duration=1000)
|
||||
if not previous:
|
||||
# global header
|
||||
first_frame = getheader(im_frame, palette, im.encoderinfo)[0]
|
||||
first_frame += getdata(im_frame, (0, 0), **im.encoderinfo)
|
||||
else:
|
||||
if first_frame:
|
||||
for s in first_frame:
|
||||
fp.write(s)
|
||||
first_frame = None
|
||||
|
||||
# delta frame
|
||||
delta = ImageChops.subtract_modulo(im_frame, previous.copy())
|
||||
bbox = delta.getbbox()
|
||||
|
||||
if bbox:
|
||||
# compress difference
|
||||
for s in getdata(im_frame.crop(bbox),
|
||||
bbox[:2], **im.encoderinfo):
|
||||
fp.write(s)
|
||||
else:
|
||||
# FIXME: what should we do in this case?
|
||||
pass
|
||||
previous = im_frame
|
||||
if first_frame:
|
||||
save_all = False
|
||||
if not save_all:
|
||||
header = getheader(im_out, palette, im.encoderinfo)[0]
|
||||
for s in header:
|
||||
fp.write(s)
|
||||
|
||||
flags = 0
|
||||
|
||||
if get_interlace(im):
|
||||
flags = flags | 64
|
||||
|
||||
# local image header
|
||||
_get_local_header(fp, im, (0, 0), flags)
|
||||
|
||||
im_out.encoderconfig = (8, get_interlace(im))
|
||||
ImageFile._save(im_out, fp, [("gif", (0, 0)+im.size, 0,
|
||||
RAWMODE[im_out.mode])])
|
||||
|
||||
fp.write(b"\0") # end of image data
|
||||
if not save_all or not _write_multiple_frames(im, fp, palette):
|
||||
_write_single_frame(im, fp, palette)
|
||||
|
||||
fp.write(b";") # end of file
|
||||
|
||||
|
@ -402,10 +476,7 @@ def _save(im, fp, filename, save_all=False):
|
|||
|
||||
|
||||
def get_interlace(im):
|
||||
try:
|
||||
interlace = im.encoderinfo["interlace"]
|
||||
except KeyError:
|
||||
interlace = 1
|
||||
interlace = im.encoderinfo.get("interlace", 1)
|
||||
|
||||
# workaround for @PIL153
|
||||
if min(im.size) < 16:
|
||||
|
@ -414,7 +485,7 @@ def get_interlace(im):
|
|||
return interlace
|
||||
|
||||
|
||||
def _get_local_header(fp, im, offset, flags):
|
||||
def _write_local_header(fp, im, offset, flags):
|
||||
transparent_color_exists = False
|
||||
try:
|
||||
transparency = im.encoderinfo["transparency"]
|
||||
|
@ -425,36 +496,41 @@ def _get_local_header(fp, im, offset, flags):
|
|||
# optimize the block away if transparent color is not used
|
||||
transparent_color_exists = True
|
||||
|
||||
if _get_optimize(im, im.encoderinfo):
|
||||
used_palette_colors = _get_used_palette_colors(im)
|
||||
|
||||
used_palette_colors = _get_optimize(im, im.encoderinfo)
|
||||
if used_palette_colors is not None:
|
||||
# adjust the transparency index after optimize
|
||||
if len(used_palette_colors) < 256:
|
||||
for i in range(len(used_palette_colors)):
|
||||
if used_palette_colors[i] == transparency:
|
||||
transparency = i
|
||||
transparent_color_exists = True
|
||||
break
|
||||
else:
|
||||
try:
|
||||
transparency = used_palette_colors.index(transparency)
|
||||
except ValueError:
|
||||
transparent_color_exists = False
|
||||
|
||||
if "duration" in im.encoderinfo:
|
||||
duration = int(im.encoderinfo["duration"] / 10)
|
||||
else:
|
||||
duration = 0
|
||||
if transparent_color_exists or duration != 0:
|
||||
transparency_flag = 1 if transparent_color_exists else 0
|
||||
|
||||
disposal = int(im.encoderinfo.get('disposal', 0))
|
||||
|
||||
if transparent_color_exists or duration != 0 or disposal:
|
||||
packed_flag = 1 if transparent_color_exists else 0
|
||||
packed_flag |= disposal << 2
|
||||
if not transparent_color_exists:
|
||||
transparency = 0
|
||||
|
||||
fp.write(b"!" +
|
||||
o8(249) + # extension intro
|
||||
o8(4) + # length
|
||||
o8(transparency_flag) + # transparency info present
|
||||
o8(packed_flag) + # packed fields
|
||||
o16(duration) + # duration
|
||||
o8(transparency) + # transparency index
|
||||
o8(0))
|
||||
|
||||
if "comment" in im.encoderinfo and 1 <= len(im.encoderinfo["comment"]) <= 255:
|
||||
fp.write(b"!" +
|
||||
o8(254) + # extension intro
|
||||
o8(len(im.encoderinfo["comment"])) +
|
||||
im.encoderinfo["comment"] +
|
||||
o8(0))
|
||||
if "loop" in im.encoderinfo:
|
||||
number_of_loops = im.encoderinfo["loop"]
|
||||
fp.write(b"!" +
|
||||
|
@ -465,17 +541,30 @@ def _get_local_header(fp, im, offset, flags):
|
|||
o8(1) +
|
||||
o16(number_of_loops) + # number of loops
|
||||
o8(0))
|
||||
include_color_table = im.encoderinfo.get('include_color_table')
|
||||
if include_color_table:
|
||||
palette = im.encoderinfo.get("palette", None)
|
||||
palette_bytes = _get_palette_bytes(im)
|
||||
color_table_size = _get_color_table_size(palette_bytes)
|
||||
if color_table_size:
|
||||
flags = flags | 128 # local color table flag
|
||||
flags = flags | color_table_size
|
||||
|
||||
fp.write(b"," +
|
||||
o16(offset[0]) + # offset
|
||||
o16(offset[1]) +
|
||||
o16(im.size[0]) + # size
|
||||
o16(im.size[1]) +
|
||||
o8(flags) + # flags
|
||||
o8(8)) # bits
|
||||
o8(flags)) # flags
|
||||
if include_color_table and color_table_size:
|
||||
fp.write(_get_header_palette(palette_bytes))
|
||||
fp.write(o8(8)) # bits
|
||||
|
||||
|
||||
def _save_netpbm(im, fp, filename):
|
||||
|
||||
# Unused by default.
|
||||
# To use, uncomment the register_save call at the end of the file.
|
||||
#
|
||||
# If you need real GIF compression and/or RGB quantization, you
|
||||
# can use the external NETPBM/PBMPLUS utilities. See comments
|
||||
|
@ -483,25 +572,21 @@ def _save_netpbm(im, fp, filename):
|
|||
|
||||
import os
|
||||
from subprocess import Popen, check_call, PIPE, CalledProcessError
|
||||
import tempfile
|
||||
file = im._dump()
|
||||
|
||||
with open(filename, 'wb') as f:
|
||||
if im.mode != "RGB":
|
||||
with open(filename, 'wb') as f:
|
||||
stderr = tempfile.TemporaryFile()
|
||||
check_call(["ppmtogif", file], stdout=f, stderr=stderr)
|
||||
with open(os.devnull, 'wb') as devnull:
|
||||
check_call(["ppmtogif", file], stdout=f, stderr=devnull)
|
||||
else:
|
||||
with open(filename, 'wb') as f:
|
||||
|
||||
# Pipe ppmquant output into ppmtogif
|
||||
# "ppmquant 256 %s | ppmtogif > %s" % (file, filename)
|
||||
quant_cmd = ["ppmquant", "256", file]
|
||||
togif_cmd = ["ppmtogif"]
|
||||
stderr = tempfile.TemporaryFile()
|
||||
quant_proc = Popen(quant_cmd, stdout=PIPE, stderr=stderr)
|
||||
stderr = tempfile.TemporaryFile()
|
||||
togif_proc = Popen(togif_cmd, stdin=quant_proc.stdout, stdout=f,
|
||||
stderr=stderr)
|
||||
with open(os.devnull, 'wb') as devnull:
|
||||
quant_proc = Popen(quant_cmd, stdout=PIPE, stderr=devnull)
|
||||
togif_proc = Popen(togif_cmd, stdin=quant_proc.stdout,
|
||||
stdout=f, stderr=devnull)
|
||||
|
||||
# Allow ppmquant to receive SIGPIPE if ppmtogif exits
|
||||
quant_proc.stdout.close()
|
||||
|
@ -520,129 +605,184 @@ def _save_netpbm(im, fp, filename):
|
|||
pass
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# GIF utilities
|
||||
# Force optimization so that we can test performance against
|
||||
# cases where it took lots of memory and time previously.
|
||||
_FORCE_OPTIMIZE = False
|
||||
|
||||
|
||||
def _get_optimize(im, info):
|
||||
return im.mode in ("P", "L") and info and info.get("optimize", 0)
|
||||
"""
|
||||
Palette optimization is a potentially expensive operation.
|
||||
|
||||
This function determines if the palette should be optimized using
|
||||
some heuristics, then returns the list of palette entries in use.
|
||||
|
||||
def _get_used_palette_colors(im):
|
||||
used_palette_colors = []
|
||||
:param im: Image object
|
||||
:param info: encoderinfo
|
||||
:returns: list of indexes of palette entries in use, or None
|
||||
"""
|
||||
if im.mode in ("P", "L") and info and info.get("optimize", 0):
|
||||
# Potentially expensive operation.
|
||||
|
||||
# The palette saves 3 bytes per color not used, but palette
|
||||
# lengths are restricted to 3*(2**N) bytes. Max saving would
|
||||
# be 768 -> 6 bytes if we went all the way down to 2 colors.
|
||||
# * If we're over 128 colors, we can't save any space.
|
||||
# * If there aren't any holes, it's not worth collapsing.
|
||||
# * If we have a 'large' image, the palette is in the noise.
|
||||
|
||||
# create the new palette if not every color is used
|
||||
optimise = _FORCE_OPTIMIZE or im.mode == 'L'
|
||||
if optimise or im.width * im.height < 512 * 512:
|
||||
# check which colors are used
|
||||
i = 0
|
||||
for count in im.histogram():
|
||||
used_palette_colors = []
|
||||
for i, count in enumerate(im.histogram()):
|
||||
if count:
|
||||
used_palette_colors.append(i)
|
||||
i += 1
|
||||
|
||||
if optimise or (len(used_palette_colors) <= 128 and
|
||||
max(used_palette_colors) > len(used_palette_colors)):
|
||||
return used_palette_colors
|
||||
|
||||
|
||||
def getheader(im, palette=None, info=None):
|
||||
"""Return a list of strings representing a GIF header"""
|
||||
|
||||
# Header Block
|
||||
# http://www.matthewflickinger.com/lab/whatsinagif/bits_and_bytes.asp
|
||||
|
||||
version = b"87a"
|
||||
for extensionKey in ["transparency", "duration", "loop"]:
|
||||
if info and extensionKey in info and \
|
||||
not (extensionKey == "duration" and info[extensionKey] == 0):
|
||||
version = b"89a"
|
||||
break
|
||||
else:
|
||||
if im.info.get("version") == "89a":
|
||||
version = b"89a"
|
||||
|
||||
header = [
|
||||
b"GIF"+version + # signature + version
|
||||
o16(im.size[0]) + # canvas width
|
||||
o16(im.size[1]) # canvas height
|
||||
]
|
||||
|
||||
if im.mode == "P":
|
||||
if palette and isinstance(palette, bytes):
|
||||
source_palette = palette[:768]
|
||||
else:
|
||||
source_palette = im.im.getpalette("RGB")[:768]
|
||||
else: # L-mode
|
||||
if palette and isinstance(palette, bytes):
|
||||
source_palette = palette[:768]
|
||||
else:
|
||||
source_palette = bytearray([i//3 for i in range(768)])
|
||||
|
||||
used_palette_colors = palette_bytes = None
|
||||
|
||||
if _get_optimize(im, info):
|
||||
used_palette_colors = _get_used_palette_colors(im)
|
||||
|
||||
# create the new palette if not every color is used
|
||||
if len(used_palette_colors) < 256:
|
||||
palette_bytes = b""
|
||||
new_positions = {}
|
||||
|
||||
i = 0
|
||||
# pick only the used colors from the palette
|
||||
for oldPosition in used_palette_colors:
|
||||
palette_bytes += source_palette[oldPosition*3:oldPosition*3+3]
|
||||
new_positions[oldPosition] = i
|
||||
i += 1
|
||||
|
||||
# replace the palette color id of all pixel with the new id
|
||||
image_bytes = bytearray(im.tobytes())
|
||||
for i in range(len(image_bytes)):
|
||||
image_bytes[i] = new_positions[image_bytes[i]]
|
||||
im.frombytes(bytes(image_bytes))
|
||||
new_palette_bytes = (palette_bytes +
|
||||
(768 - len(palette_bytes)) * b'\x00')
|
||||
im.putpalette(new_palette_bytes)
|
||||
im.palette = ImagePalette.ImagePalette("RGB",
|
||||
palette=palette_bytes,
|
||||
size=len(palette_bytes))
|
||||
|
||||
if not palette_bytes:
|
||||
palette_bytes = source_palette
|
||||
|
||||
# Logical Screen Descriptor
|
||||
def _get_color_table_size(palette_bytes):
|
||||
# calculate the palette size for the header
|
||||
import math
|
||||
color_table_size = int(math.ceil(math.log(len(palette_bytes)//3, 2)))-1
|
||||
if color_table_size < 0:
|
||||
color_table_size = 0
|
||||
# size of global color table + global color table flag
|
||||
header.append(o8(color_table_size + 128))
|
||||
# background + reserved/aspect
|
||||
if info and "background" in info:
|
||||
background = info["background"]
|
||||
elif "background" in im.info:
|
||||
# This elif is redundant within GifImagePlugin
|
||||
# since im.info parameters are bundled into the info dictionary
|
||||
# However, external scripts may call getheader directly
|
||||
# So this maintains earlier behaviour
|
||||
background = im.info["background"]
|
||||
else:
|
||||
background = 0
|
||||
header.append(o8(background) + o8(0))
|
||||
# end of Logical Screen Descriptor
|
||||
return color_table_size
|
||||
|
||||
|
||||
def _get_header_palette(palette_bytes):
|
||||
"""
|
||||
Returns the palette, null padded to the next power of 2 (*3) bytes
|
||||
suitable for direct inclusion in the GIF header
|
||||
|
||||
:param palette_bytes: Unpadded palette bytes, in RGBRGB form
|
||||
:returns: Null padded palette
|
||||
"""
|
||||
color_table_size = _get_color_table_size(palette_bytes)
|
||||
|
||||
# add the missing amount of bytes
|
||||
# the palette has to be 2<<n in size
|
||||
actual_target_size_diff = (2 << color_table_size) - len(palette_bytes)//3
|
||||
if actual_target_size_diff > 0:
|
||||
palette_bytes += o8(0) * 3 * actual_target_size_diff
|
||||
return palette_bytes
|
||||
|
||||
|
||||
def _get_palette_bytes(im):
|
||||
"""
|
||||
Gets the palette for inclusion in the gif header
|
||||
|
||||
:param im: Image object
|
||||
:returns: Bytes, len<=768 suitable for inclusion in gif header
|
||||
"""
|
||||
return im.palette.palette
|
||||
|
||||
|
||||
def _get_global_header(im, info):
|
||||
"""Return a list of strings representing a GIF header"""
|
||||
|
||||
# Header Block
|
||||
# http://www.matthewflickinger.com/lab/whatsinagif/bits_and_bytes.asp
|
||||
|
||||
version = b"87a"
|
||||
for extensionKey in ["transparency", "duration", "loop", "comment"]:
|
||||
if info and extensionKey in info:
|
||||
if ((extensionKey == "duration" and info[extensionKey] == 0) or
|
||||
(extensionKey == "comment" and not (1 <= len(info[extensionKey]) <= 255))):
|
||||
continue
|
||||
version = b"89a"
|
||||
break
|
||||
else:
|
||||
if im.info.get("version") == b"89a":
|
||||
version = b"89a"
|
||||
|
||||
palette_bytes = _get_palette_bytes(im)
|
||||
color_table_size = _get_color_table_size(palette_bytes)
|
||||
|
||||
background = info["background"] if "background" in info else 0
|
||||
|
||||
return [
|
||||
b"GIF"+version + # signature + version
|
||||
o16(im.size[0]) + # canvas width
|
||||
o16(im.size[1]), # canvas height
|
||||
|
||||
# Logical Screen Descriptor
|
||||
# size of global color table + global color table flag
|
||||
o8(color_table_size + 128), # packed fields
|
||||
# background + reserved/aspect
|
||||
o8(background) + o8(0),
|
||||
|
||||
# Global Color Table
|
||||
_get_header_palette(palette_bytes)
|
||||
]
|
||||
|
||||
|
||||
def _write_frame_data(fp, im_frame, offset, params):
|
||||
try:
|
||||
im_frame.encoderinfo = params
|
||||
|
||||
# local image header
|
||||
_write_local_header(fp, im_frame, offset, 0)
|
||||
|
||||
ImageFile._save(im_frame, fp, [("gif", (0, 0)+im_frame.size, 0,
|
||||
RAWMODE[im_frame.mode])])
|
||||
|
||||
fp.write(b"\0") # end of image data
|
||||
finally:
|
||||
del im_frame.encoderinfo
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# Legacy GIF utilities
|
||||
|
||||
|
||||
def getheader(im, palette=None, info=None):
|
||||
"""
|
||||
Legacy Method to get Gif data from image.
|
||||
|
||||
Warning:: May modify image data.
|
||||
|
||||
:param im: Image object
|
||||
:param palette: bytes object containing the source palette, or ....
|
||||
:param info: encoderinfo
|
||||
:returns: tuple of(list of header items, optimized palette)
|
||||
|
||||
"""
|
||||
used_palette_colors = _get_optimize(im, info)
|
||||
|
||||
if info is None:
|
||||
info = {}
|
||||
|
||||
if "background" not in info and "background" in im.info:
|
||||
info["background"] = im.info["background"]
|
||||
|
||||
im_mod = _normalize_palette(im, palette, info)
|
||||
im.palette = im_mod.palette
|
||||
im.im = im_mod.im
|
||||
header = _get_global_header(im, info)
|
||||
|
||||
# Header + Logical Screen Descriptor + Global Color Table
|
||||
header.append(palette_bytes)
|
||||
return header, used_palette_colors
|
||||
|
||||
|
||||
# To specify duration, add the time in milliseconds to getdata(),
|
||||
# e.g. getdata(im_frame, duration=1000)
|
||||
def getdata(im, offset=(0, 0), **params):
|
||||
"""Return a list of strings representing this image.
|
||||
The first string is a local image header, the rest contains
|
||||
encoded image data."""
|
||||
"""
|
||||
Legacy Method
|
||||
|
||||
Return a list of strings representing this image.
|
||||
The first string is a local image header, the rest contains
|
||||
encoded image data.
|
||||
|
||||
:param im: Image object
|
||||
:param offset: Tuple of (x, y) pixels. Defaults to (0,0)
|
||||
:param \\**params: E.g. duration or other encoder info parameters
|
||||
:returns: List of Bytes containing gif encoded frame data
|
||||
|
||||
"""
|
||||
class Collector(object):
|
||||
data = []
|
||||
|
||||
|
@ -653,18 +793,7 @@ def getdata(im, offset=(0, 0), **params):
|
|||
|
||||
fp = Collector()
|
||||
|
||||
try:
|
||||
im.encoderinfo = params
|
||||
|
||||
# local image header
|
||||
_get_local_header(fp, im, offset, 0)
|
||||
|
||||
ImageFile._save(im, fp, [("gif", (0, 0)+im.size, 0, RAWMODE[im.mode])])
|
||||
|
||||
fp.write(b"\0") # end of image data
|
||||
|
||||
finally:
|
||||
del im.encoderinfo
|
||||
_write_frame_data(fp, im, offset, params)
|
||||
|
||||
return fp.data
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
#
|
||||
|
||||
from math import pi, log, sin, sqrt
|
||||
from PIL._binary import o8
|
||||
from ._binary import o8
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# Stuff to translate curve segments to palette values (derived from
|
||||
|
@ -55,6 +55,7 @@ def sphere_increasing(middle, pos):
|
|||
def sphere_decreasing(middle, pos):
|
||||
return 1.0 - sqrt(1.0 - linear(middle, pos) ** 2)
|
||||
|
||||
|
||||
SEGMENTS = [linear, curved, sine, sphere_increasing, sphere_decreasing]
|
||||
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
#
|
||||
|
||||
import re
|
||||
from PIL._binary import o8
|
||||
from ._binary import o8
|
||||
|
||||
|
||||
##
|
||||
|
@ -41,7 +41,7 @@ class GimpPaletteFile(object):
|
|||
if not s:
|
||||
break
|
||||
# skip fields and comment lines
|
||||
if re.match(b"\w+:|#", s):
|
||||
if re.match(br"\w+:|#", s):
|
||||
continue
|
||||
if len(s) > 100:
|
||||
raise SyntaxError("bad palette file")
|
||||
|
|
|
@ -9,17 +9,18 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from PIL import Image, ImageFile
|
||||
from . import Image, ImageFile
|
||||
from ._binary import i8
|
||||
|
||||
_handler = None
|
||||
|
||||
|
||||
##
|
||||
# Install application-specific GRIB image handler.
|
||||
#
|
||||
# @param handler Handler object.
|
||||
|
||||
def register_handler(handler):
|
||||
"""
|
||||
Install application-specific GRIB image handler.
|
||||
|
||||
:param handler: Handler object.
|
||||
"""
|
||||
global _handler
|
||||
_handler = handler
|
||||
|
||||
|
@ -28,7 +29,7 @@ def register_handler(handler):
|
|||
# Image adapter
|
||||
|
||||
def _accept(prefix):
|
||||
return prefix[0:4] == b"GRIB" and prefix[7] == b'\x01'
|
||||
return prefix[0:4] == b"GRIB" and i8(prefix[7]) == 1
|
||||
|
||||
|
||||
class GribStubImageFile(ImageFile.StubImageFile):
|
||||
|
|
|
@ -9,17 +9,17 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from PIL import Image, ImageFile
|
||||
from . import Image, ImageFile
|
||||
|
||||
_handler = None
|
||||
|
||||
|
||||
##
|
||||
# Install application-specific HDF5 image handler.
|
||||
#
|
||||
# @param handler Handler object.
|
||||
|
||||
def register_handler(handler):
|
||||
"""
|
||||
Install application-specific HDF5 image handler.
|
||||
|
||||
:param handler: Handler object.
|
||||
"""
|
||||
global _handler
|
||||
_handler = handler
|
||||
|
||||
|
@ -69,5 +69,4 @@ def _save(im, fp, filename):
|
|||
Image.register_open(HDF5StubImageFile.format, HDF5StubImageFile, _accept)
|
||||
Image.register_save(HDF5StubImageFile.format, _save)
|
||||
|
||||
Image.register_extension(HDF5StubImageFile.format, ".h5")
|
||||
Image.register_extension(HDF5StubImageFile.format, ".hdf")
|
||||
Image.register_extensions(HDF5StubImageFile.format, [".h5", ".hdf"])
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
# The Python Imaging Library.
|
||||
# $Id$
|
||||
#
|
||||
# Mac OS X icns file decoder, based on icns.py by Bob Ippolito.
|
||||
# macOS icns file decoder, based on icns.py by Bob Ippolito.
|
||||
#
|
||||
# history:
|
||||
# 2004-10-09 fl Turned into a PIL plugin; removed 2.3 dependencies.
|
||||
|
@ -15,7 +15,8 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from PIL import Image, ImageFile, PngImagePlugin, _binary
|
||||
from PIL import Image, ImageFile, PngImagePlugin
|
||||
from PIL._binary import i8
|
||||
import io
|
||||
import os
|
||||
import shutil
|
||||
|
@ -27,8 +28,6 @@ enable_jpeg2k = hasattr(Image.core, 'jp2klib_version')
|
|||
if enable_jpeg2k:
|
||||
from PIL import Jpeg2KImagePlugin
|
||||
|
||||
i8 = _binary.i8
|
||||
|
||||
HEADERSIZE = 8
|
||||
|
||||
|
||||
|
@ -302,36 +301,39 @@ def _save(im, fp, filename):
|
|||
"""
|
||||
Saves the image as a series of PNG files,
|
||||
that are then converted to a .icns file
|
||||
using the OS X command line utility 'iconutil'.
|
||||
using the macOS command line utility 'iconutil'.
|
||||
|
||||
OS X only.
|
||||
macOS only.
|
||||
"""
|
||||
if hasattr(fp, "flush"):
|
||||
fp.flush()
|
||||
|
||||
# create the temporary set of pngs
|
||||
iconset = tempfile.mkdtemp('.iconset')
|
||||
provided_images = {im.width: im
|
||||
for im in im.encoderinfo.get("append_images", [])}
|
||||
last_w = None
|
||||
last_im = None
|
||||
for w in [16, 32, 128, 256, 512]:
|
||||
prefix = 'icon_{}x{}'.format(w, w)
|
||||
|
||||
first_path = os.path.join(iconset, prefix+'.png')
|
||||
if last_w == w:
|
||||
im_scaled = last_im
|
||||
shutil.copyfile(second_path, first_path)
|
||||
else:
|
||||
im_scaled = im.resize((w, w), Image.LANCZOS)
|
||||
im_scaled.save(os.path.join(iconset, prefix+'.png'))
|
||||
im_w = provided_images.get(w, im.resize((w, w), Image.LANCZOS))
|
||||
im_w.save(first_path)
|
||||
|
||||
im_scaled = im.resize((w*2, w*2), Image.LANCZOS)
|
||||
im_scaled.save(os.path.join(iconset, prefix+'@2x.png'))
|
||||
last_im = im_scaled
|
||||
second_path = os.path.join(iconset, prefix+'@2x.png')
|
||||
im_w2 = provided_images.get(w*2, im.resize((w*2, w*2), Image.LANCZOS))
|
||||
im_w2.save(second_path)
|
||||
last_w = w*2
|
||||
|
||||
# iconutil -c icns -o {} {}
|
||||
from subprocess import Popen, PIPE, CalledProcessError
|
||||
|
||||
convert_cmd = ["iconutil", "-c", "icns", "-o", filename, iconset]
|
||||
stderr = tempfile.TemporaryFile()
|
||||
convert_proc = Popen(convert_cmd, stdout=PIPE, stderr=stderr)
|
||||
with open(os.devnull, 'wb') as devnull:
|
||||
convert_proc = Popen(convert_cmd, stdout=PIPE, stderr=devnull)
|
||||
|
||||
convert_proc.stdout.close()
|
||||
|
||||
|
@ -343,6 +345,7 @@ def _save(im, fp, filename):
|
|||
if retcode:
|
||||
raise CalledProcessError(retcode, convert_cmd)
|
||||
|
||||
|
||||
Image.register_open(IcnsImageFile.format, IcnsImageFile,
|
||||
lambda x: x[:4] == b'icns')
|
||||
Image.register_extension(IcnsImageFile.format, '.icns')
|
||||
|
@ -354,13 +357,18 @@ if sys.platform == 'darwin':
|
|||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
if len(sys.argv) < 2:
|
||||
print("Syntax: python IcnsImagePlugin.py [file]")
|
||||
sys.exit()
|
||||
|
||||
imf = IcnsImageFile(open(sys.argv[1], 'rb'))
|
||||
for size in imf.info['sizes']:
|
||||
imf.size = size
|
||||
imf.load()
|
||||
im = imf.im
|
||||
im.save('out-%s-%s-%s.png' % size)
|
||||
im = Image.open(open(sys.argv[1], "rb"))
|
||||
im = Image.open(sys.argv[1])
|
||||
im.save("out.png")
|
||||
if sys.platform == 'windows':
|
||||
os.startfile("out.png")
|
||||
|
|
|
@ -15,17 +15,18 @@
|
|||
|
||||
# This plugin is a refactored version of Win32IconImagePlugin by Bryan Davis
|
||||
# <casadebender@gmail.com>.
|
||||
# https://code.google.com/p/casadebender/wiki/Win32IconImagePlugin
|
||||
# https://code.google.com/archive/p/casadebender/wikis/Win32IconImagePlugin.wiki
|
||||
#
|
||||
# Icon format references:
|
||||
# * https://en.wikipedia.org/wiki/ICO_(file_format)
|
||||
# * http://msdn.microsoft.com/en-us/library/ms997538.aspx
|
||||
# * https://msdn.microsoft.com/en-us/library/ms997538.aspx
|
||||
|
||||
|
||||
import struct
|
||||
from io import BytesIO
|
||||
|
||||
from PIL import Image, ImageFile, BmpImagePlugin, PngImagePlugin, _binary
|
||||
from . import Image, ImageFile, BmpImagePlugin, PngImagePlugin
|
||||
from ._binary import i8, i16le as i16, i32le as i32
|
||||
from math import log, ceil
|
||||
|
||||
__version__ = "0.1"
|
||||
|
@ -33,10 +34,6 @@ __version__ = "0.1"
|
|||
#
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
i8 = _binary.i8
|
||||
i16 = _binary.i16le
|
||||
i32 = _binary.i32le
|
||||
|
||||
_MAGIC = b"\0\0\1\0"
|
||||
|
||||
|
||||
|
@ -44,16 +41,19 @@ def _save(im, fp, filename):
|
|||
fp.write(_MAGIC) # (2+2)
|
||||
sizes = im.encoderinfo.get("sizes",
|
||||
[(16, 16), (24, 24), (32, 32), (48, 48),
|
||||
(64, 64), (128, 128), (255, 255)])
|
||||
(64, 64), (128, 128), (256, 256)])
|
||||
width, height = im.size
|
||||
filter(lambda x: False if (x[0] > width or x[1] > height or
|
||||
x[0] > 255 or x[1] > 255) else True, sizes)
|
||||
sizes = filter(lambda x: False if (x[0] > width or x[1] > height or
|
||||
x[0] > 256 or x[1] > 256) else True,
|
||||
sizes)
|
||||
sizes = list(sizes)
|
||||
fp.write(struct.pack("<H", len(sizes))) # idCount(2)
|
||||
offset = fp.tell() + len(sizes)*16
|
||||
for size in sizes:
|
||||
width, height = size
|
||||
fp.write(struct.pack("B", width)) # bWidth(1)
|
||||
fp.write(struct.pack("B", height)) # bHeight(1)
|
||||
# 0 means 256
|
||||
fp.write(struct.pack("B", width if width < 256 else 0)) # bWidth(1)
|
||||
fp.write(struct.pack("B", height if height < 256 else 0)) # bHeight(1)
|
||||
fp.write(b"\0") # bColorCount(1)
|
||||
fp.write(b"\0") # bReserved(1)
|
||||
fp.write(b"\0\0") # wPlanes(2)
|
||||
|
@ -139,7 +139,7 @@ class IcoFile(object):
|
|||
"""
|
||||
Get a list of all available icon sizes and color depths.
|
||||
"""
|
||||
return set((h['width'], h['height']) for h in self.entry)
|
||||
return {(h['width'], h['height']) for h in self.entry}
|
||||
|
||||
def getimage(self, size, bpp=False):
|
||||
"""
|
||||
|
@ -176,8 +176,8 @@ class IcoFile(object):
|
|||
# figure out where AND mask image starts
|
||||
mode = a[0]
|
||||
bpp = 8
|
||||
for k in BmpImagePlugin.BIT2MODE.keys():
|
||||
if mode == BmpImagePlugin.BIT2MODE[k][1]:
|
||||
for k, v in BmpImagePlugin.BIT2MODE.items():
|
||||
if mode == v[1]:
|
||||
bpp = k
|
||||
break
|
||||
|
||||
|
@ -215,13 +215,13 @@ class IcoFile(object):
|
|||
total_bytes = int((w * im.size[1]) / 8)
|
||||
|
||||
self.buf.seek(and_mask_offset)
|
||||
maskData = self.buf.read(total_bytes)
|
||||
mask_data = self.buf.read(total_bytes)
|
||||
|
||||
# convert raw data to image
|
||||
mask = Image.frombuffer(
|
||||
'1', # 1 bpp
|
||||
im.size, # (w, h)
|
||||
maskData, # source chars
|
||||
mask_data, # source chars
|
||||
'raw', # raw decoder
|
||||
('1;I', int(w/8), -1) # 1bpp inverted, padded, reversed
|
||||
)
|
||||
|
@ -252,7 +252,7 @@ class IcoImageFile(ImageFile.ImageFile):
|
|||
|
||||
This plugin is a refactored version of Win32IconImagePlugin by Bryan Davis
|
||||
<casadebender@gmail.com>.
|
||||
https://code.google.com/p/casadebender/wiki/Win32IconImagePlugin
|
||||
https://code.google.com/archive/p/casadebender/wikis/Win32IconImagePlugin.wiki
|
||||
"""
|
||||
format = "ICO"
|
||||
format_description = "Windows Icon"
|
||||
|
@ -278,6 +278,7 @@ class IcoImageFile(ImageFile.ImageFile):
|
|||
#
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
|
||||
Image.register_open(IcoImageFile.format, IcoImageFile, _accept)
|
||||
Image.register_save(IcoImageFile.format, _save)
|
||||
Image.register_extension(IcoImageFile.format, ".ico")
|
||||
|
|
|
@ -27,8 +27,8 @@
|
|||
|
||||
|
||||
import re
|
||||
from PIL import Image, ImageFile, ImagePalette
|
||||
from PIL._binary import i8
|
||||
from . import Image, ImageFile, ImagePalette
|
||||
from ._binary import i8
|
||||
|
||||
__version__ = "0.7"
|
||||
|
||||
|
@ -109,6 +109,7 @@ class ImImageFile(ImageFile.ImageFile):
|
|||
|
||||
format = "IM"
|
||||
format_description = "IFUNC Image Memory"
|
||||
_close_exclusive_fp_after_loading = False
|
||||
|
||||
def _open(self):
|
||||
|
||||
|
@ -269,11 +270,7 @@ class ImImageFile(ImageFile.ImageFile):
|
|||
return self.info[FRAMES] > 1
|
||||
|
||||
def seek(self, frame):
|
||||
|
||||
if frame < 0 or frame >= self.info[FRAMES]:
|
||||
raise EOFError("seek outside sequence")
|
||||
|
||||
if self.frame == frame:
|
||||
if not self._seek_check(frame):
|
||||
return
|
||||
|
||||
self.frame = frame
|
||||
|
@ -291,13 +288,13 @@ class ImImageFile(ImageFile.ImageFile):
|
|||
self.tile = [("raw", (0, 0)+self.size, offs, (self.rawmode, 0, -1))]
|
||||
|
||||
def tell(self):
|
||||
|
||||
return self.frame
|
||||
|
||||
#
|
||||
# --------------------------------------------------------------------
|
||||
# Save IM files
|
||||
|
||||
|
||||
SAVE = {
|
||||
# mode: (im type, raw mode)
|
||||
"1": ("0 1", "1"),
|
||||
|
@ -318,20 +315,14 @@ SAVE = {
|
|||
}
|
||||
|
||||
|
||||
def _save(im, fp, filename, check=0):
|
||||
def _save(im, fp, filename):
|
||||
|
||||
try:
|
||||
image_type, rawmode = SAVE[im.mode]
|
||||
except KeyError:
|
||||
raise ValueError("Cannot save %s images as IM" % im.mode)
|
||||
|
||||
try:
|
||||
frames = im.encoderinfo["frames"]
|
||||
except KeyError:
|
||||
frames = 1
|
||||
|
||||
if check:
|
||||
return check
|
||||
frames = im.encoderinfo.get("frames", 1)
|
||||
|
||||
fp.write(("Image type: %s image\r\n" % image_type).encode('ascii'))
|
||||
if filename:
|
||||
|
@ -349,6 +340,7 @@ def _save(im, fp, filename, check=0):
|
|||
# --------------------------------------------------------------------
|
||||
# Registry
|
||||
|
||||
|
||||
Image.register_open(ImImageFile.format, ImImageFile)
|
||||
Image.register_save(ImImageFile.format, _save)
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -15,7 +15,7 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from PIL import Image
|
||||
from . import Image
|
||||
|
||||
|
||||
def constant(image, value):
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# The Python Imaging Library.
|
||||
# $Id$
|
||||
|
||||
# Optional color managment support, based on Kevin Cazabon's PyCMS
|
||||
# Optional color management support, based on Kevin Cazabon's PyCMS
|
||||
# library.
|
||||
|
||||
# History:
|
||||
|
@ -18,6 +18,16 @@
|
|||
from __future__ import print_function
|
||||
import sys
|
||||
|
||||
from PIL import Image
|
||||
try:
|
||||
from PIL import _imagingcms
|
||||
except ImportError as ex:
|
||||
# Allow error import for doc purposes, but error out when accessing
|
||||
# anything in core.
|
||||
from _util import deferred_error
|
||||
_imagingcms = deferred_error(ex)
|
||||
from PIL._util import isStringType
|
||||
|
||||
DESCRIPTION = """
|
||||
pyCMS
|
||||
|
||||
|
@ -85,16 +95,6 @@ VERSION = "1.0.0 pil"
|
|||
|
||||
# --------------------------------------------------------------------.
|
||||
|
||||
from PIL import Image
|
||||
try:
|
||||
from PIL import _imagingcms
|
||||
except ImportError as ex:
|
||||
# Allow error import for doc purposes, but error out when accessing
|
||||
# anything in core.
|
||||
from _util import deferred_error
|
||||
_imagingcms = deferred_error(ex)
|
||||
from PIL._util import isStringType
|
||||
|
||||
core = _imagingcms
|
||||
|
||||
#
|
||||
|
@ -162,8 +162,10 @@ class ImageCmsProfile(object):
|
|||
self._set(core.profile_open(profile), profile)
|
||||
elif hasattr(profile, "read"):
|
||||
self._set(core.profile_frombytes(profile.read()))
|
||||
elif isinstance(profile, _imagingcms.CmsProfile):
|
||||
self._set(profile)
|
||||
else:
|
||||
self._set(profile) # assume it's already a profile
|
||||
raise TypeError("Invalid type for Profile")
|
||||
|
||||
def _set(self, profile, filename=None):
|
||||
self.profile = profile
|
||||
|
@ -188,10 +190,12 @@ class ImageCmsProfile(object):
|
|||
|
||||
class ImageCmsTransform(Image.ImagePointHandler):
|
||||
|
||||
# Transform. This can be used with the procedural API, or with the
|
||||
# standard Image.point() method.
|
||||
#
|
||||
# Will return the output profile in the output.info['icc_profile'].
|
||||
"""
|
||||
Transform. This can be used with the procedural API, or with the standard
|
||||
Image.point() method.
|
||||
|
||||
Will return the output profile in the output.info['icc_profile'].
|
||||
"""
|
||||
|
||||
def __init__(self, input, output, input_mode, output_mode,
|
||||
intent=INTENT_PERCEPTUAL, proof=None,
|
||||
|
@ -359,7 +363,7 @@ def getOpenProfile(profileFilename):
|
|||
The PyCMSProfile object can be passed back into pyCMS for use in creating
|
||||
transforms and such (as in ImageCms.buildTransformFromOpenProfiles()).
|
||||
|
||||
If profileFilename is not a vaild filename for an ICC profile, a PyCMSError
|
||||
If profileFilename is not a valid filename for an ICC profile, a PyCMSError
|
||||
will be raised.
|
||||
|
||||
:param profileFilename: String, as a valid filename path to the ICC profile
|
||||
|
@ -550,6 +554,7 @@ def buildProofTransform(
|
|||
except (IOError, TypeError, ValueError) as v:
|
||||
raise PyCMSError(v)
|
||||
|
||||
|
||||
buildTransformFromOpenProfiles = buildTransform
|
||||
buildProofTransformFromOpenProfiles = buildProofTransform
|
||||
|
||||
|
@ -590,7 +595,8 @@ def applyTransform(im, transform, inPlace=0):
|
|||
with the transform applied is returned (and im is not changed). The
|
||||
default is False.
|
||||
:returns: Either None, or a new PIL Image object, depending on the value of
|
||||
inPlace. The profile will be returned in the image's info['icc_profile'].
|
||||
inPlace. The profile will be returned in the image's
|
||||
info['icc_profile'].
|
||||
:exception PyCMSError:
|
||||
"""
|
||||
|
||||
|
@ -947,24 +953,3 @@ def versions():
|
|||
VERSION, core.littlecms_version,
|
||||
sys.version.split()[0], Image.VERSION
|
||||
)
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
if __name__ == "__main__":
|
||||
# create a cheap manual from the __doc__ strings for the functions above
|
||||
|
||||
print(__doc__)
|
||||
|
||||
for f in dir(sys.modules[__name__]):
|
||||
doc = None
|
||||
try:
|
||||
exec("doc = %s.__doc__" % (f))
|
||||
if "pyCMS" in doc:
|
||||
# so we don't get the __doc__ string for imported modules
|
||||
print("=" * 80)
|
||||
print("%s" % f)
|
||||
print(doc)
|
||||
except (AttributeError, TypeError):
|
||||
pass
|
||||
|
||||
# End of file
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from PIL import Image
|
||||
from . import Image
|
||||
import re
|
||||
|
||||
|
||||
|
@ -31,50 +31,63 @@ def getrgb(color):
|
|||
:param color: A color string
|
||||
:return: ``(red, green, blue[, alpha])``
|
||||
"""
|
||||
try:
|
||||
rgb = colormap[color]
|
||||
except KeyError:
|
||||
try:
|
||||
# fall back on case-insensitive lookup
|
||||
rgb = colormap[color.lower()]
|
||||
except KeyError:
|
||||
rgb = None
|
||||
# found color in cache
|
||||
color = color.lower()
|
||||
|
||||
rgb = colormap.get(color, None)
|
||||
if rgb:
|
||||
if isinstance(rgb, tuple):
|
||||
return rgb
|
||||
colormap[color] = rgb = getrgb(rgb)
|
||||
return rgb
|
||||
|
||||
# check for known string formats
|
||||
m = re.match("#\w\w\w$", color)
|
||||
if m:
|
||||
if re.match('#[a-f0-9]{3}$', color):
|
||||
return (
|
||||
int(color[1]*2, 16),
|
||||
int(color[2]*2, 16),
|
||||
int(color[3]*2, 16)
|
||||
int(color[3]*2, 16),
|
||||
)
|
||||
m = re.match("#\w\w\w\w\w\w$", color)
|
||||
if m:
|
||||
|
||||
if re.match('#[a-f0-9]{4}$', color):
|
||||
return (
|
||||
int(color[1]*2, 16),
|
||||
int(color[2]*2, 16),
|
||||
int(color[3]*2, 16),
|
||||
int(color[4]*2, 16),
|
||||
)
|
||||
|
||||
if re.match('#[a-f0-9]{6}$', color):
|
||||
return (
|
||||
int(color[1:3], 16),
|
||||
int(color[3:5], 16),
|
||||
int(color[5:7], 16)
|
||||
int(color[5:7], 16),
|
||||
)
|
||||
m = re.match("rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$", color)
|
||||
|
||||
if re.match('#[a-f0-9]{8}$', color):
|
||||
return (
|
||||
int(color[1:3], 16),
|
||||
int(color[3:5], 16),
|
||||
int(color[5:7], 16),
|
||||
int(color[7:9], 16),
|
||||
)
|
||||
|
||||
m = re.match(r"rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$", color)
|
||||
if m:
|
||||
return (
|
||||
int(m.group(1)),
|
||||
int(m.group(2)),
|
||||
int(m.group(3))
|
||||
)
|
||||
m = re.match("rgb\(\s*(\d+)%\s*,\s*(\d+)%\s*,\s*(\d+)%\s*\)$", color)
|
||||
|
||||
m = re.match(r"rgb\(\s*(\d+)%\s*,\s*(\d+)%\s*,\s*(\d+)%\s*\)$", color)
|
||||
if m:
|
||||
return (
|
||||
int((int(m.group(1)) * 255) / 100.0 + 0.5),
|
||||
int((int(m.group(2)) * 255) / 100.0 + 0.5),
|
||||
int((int(m.group(3)) * 255) / 100.0 + 0.5)
|
||||
)
|
||||
m = re.match("hsl\(\s*(\d+)\s*,\s*(\d+)%\s*,\s*(\d+)%\s*\)$", color)
|
||||
|
||||
m = re.match(r"hsl\(\s*(\d+\.?\d*)\s*,\s*(\d+\.?\d*)%\s*,\s*(\d+\.?\d*)%\s*\)$", color)
|
||||
if m:
|
||||
from colorsys import hls_to_rgb
|
||||
rgb = hls_to_rgb(
|
||||
|
@ -87,7 +100,22 @@ def getrgb(color):
|
|||
int(rgb[1] * 255 + 0.5),
|
||||
int(rgb[2] * 255 + 0.5)
|
||||
)
|
||||
m = re.match("rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$",
|
||||
|
||||
m = re.match(r"hs[bv]\(\s*(\d+\.?\d*)\s*,\s*(\d+\.?\d*)%\s*,\s*(\d+\.?\d*)%\s*\)$", color)
|
||||
if m:
|
||||
from colorsys import hsv_to_rgb
|
||||
rgb = hsv_to_rgb(
|
||||
float(m.group(1)) / 360.0,
|
||||
float(m.group(2)) / 100.0,
|
||||
float(m.group(3)) / 100.0,
|
||||
)
|
||||
return (
|
||||
int(rgb[0] * 255 + 0.5),
|
||||
int(rgb[1] * 255 + 0.5),
|
||||
int(rgb[2] * 255 + 0.5)
|
||||
)
|
||||
|
||||
m = re.match(r"rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$",
|
||||
color)
|
||||
if m:
|
||||
return (
|
||||
|
@ -125,8 +153,9 @@ def getcolor(color, mode):
|
|||
return color + (alpha,)
|
||||
return color
|
||||
|
||||
|
||||
colormap = {
|
||||
# X11 colour table (from "CSS3 module: Color working draft"), with
|
||||
# X11 colour table from https://drafts.csswg.org/css-color-4/, with
|
||||
# gray/grey spelling issues fixed. This is a superset of HTML 4.0
|
||||
# colour names used in CSS 1.
|
||||
"aliceblue": "#f0f8ff",
|
||||
|
@ -248,6 +277,7 @@ colormap = {
|
|||
"plum": "#dda0dd",
|
||||
"powderblue": "#b0e0e6",
|
||||
"purple": "#800080",
|
||||
"rebeccapurple": "#663399",
|
||||
"red": "#ff0000",
|
||||
"rosybrown": "#bc8f8f",
|
||||
"royalblue": "#4169e1",
|
||||
|
|
|
@ -31,31 +31,31 @@
|
|||
#
|
||||
|
||||
import numbers
|
||||
import warnings
|
||||
|
||||
from PIL import Image, ImageColor
|
||||
from PIL._util import isStringType
|
||||
from . import Image, ImageColor
|
||||
from ._util import isStringType
|
||||
|
||||
##
|
||||
# A simple 2D drawing interface for PIL images.
|
||||
# <p>
|
||||
# Application code should use the <b>Draw</b> factory, instead of
|
||||
# directly.
|
||||
"""
|
||||
A simple 2D drawing interface for PIL images.
|
||||
<p>
|
||||
Application code should use the <b>Draw</b> factory, instead of
|
||||
directly.
|
||||
"""
|
||||
|
||||
|
||||
class ImageDraw(object):
|
||||
|
||||
##
|
||||
# Create a drawing instance.
|
||||
#
|
||||
# @param im The image to draw in.
|
||||
# @param mode Optional mode to use for color values. For RGB
|
||||
# images, this argument can be RGB or RGBA (to blend the
|
||||
# drawing into the image). For all other modes, this argument
|
||||
# must be the same as the image mode. If omitted, the mode
|
||||
# defaults to the mode of the image.
|
||||
|
||||
def __init__(self, im, mode=None):
|
||||
"""
|
||||
Create a drawing instance.
|
||||
|
||||
:param im: The image to draw in.
|
||||
:param mode: Optional mode to use for color values. For RGB
|
||||
images, this argument can be RGB or RGBA (to blend the
|
||||
drawing into the image). For all other modes, this argument
|
||||
must be the same as the image mode. If omitted, the mode
|
||||
defaults to the mode of the image.
|
||||
"""
|
||||
im.load()
|
||||
if im.readonly:
|
||||
im._copy() # make it writeable
|
||||
|
@ -86,27 +86,14 @@ class ImageDraw(object):
|
|||
self.fill = 0
|
||||
self.font = None
|
||||
|
||||
def setink(self, ink):
|
||||
raise Exception("setink() has been removed. " +
|
||||
"Please use keyword arguments instead.")
|
||||
|
||||
def setfill(self, onoff):
|
||||
raise Exception("setfill() has been removed. " +
|
||||
"Please use keyword arguments instead.")
|
||||
|
||||
def setfont(self, font):
|
||||
warnings.warn("setfont() is deprecated. " +
|
||||
"Please set the attribute directly instead.")
|
||||
# compatibility
|
||||
self.font = font
|
||||
|
||||
##
|
||||
# Get the current default font.
|
||||
|
||||
def getfont(self):
|
||||
"""
|
||||
Get the current default font.
|
||||
|
||||
:returns: An image font."""
|
||||
if not self.font:
|
||||
# FIXME: should add a font repository
|
||||
from PIL import ImageFont
|
||||
from . import ImageFont
|
||||
self.font = ImageFont.load_default()
|
||||
return self.font
|
||||
|
||||
|
@ -131,18 +118,14 @@ class ImageDraw(object):
|
|||
fill = self.draw.draw_ink(fill, self.mode)
|
||||
return ink, fill
|
||||
|
||||
##
|
||||
# Draw an arc.
|
||||
|
||||
def arc(self, xy, start, end, fill=None):
|
||||
"""Draw an arc."""
|
||||
ink, fill = self._getink(fill)
|
||||
if ink is not None:
|
||||
self.draw.draw_arc(xy, start, end, ink)
|
||||
|
||||
##
|
||||
# Draw a bitmap.
|
||||
|
||||
def bitmap(self, xy, bitmap, fill=None):
|
||||
"""Draw a bitmap."""
|
||||
bitmap.load()
|
||||
ink, fill = self._getink(fill)
|
||||
if ink is None:
|
||||
|
@ -150,39 +133,30 @@ class ImageDraw(object):
|
|||
if ink is not None:
|
||||
self.draw.draw_bitmap(xy, bitmap.im, ink)
|
||||
|
||||
##
|
||||
# Draw a chord.
|
||||
|
||||
def chord(self, xy, start, end, fill=None, outline=None):
|
||||
"""Draw a chord."""
|
||||
ink, fill = self._getink(outline, fill)
|
||||
if fill is not None:
|
||||
self.draw.draw_chord(xy, start, end, fill, 1)
|
||||
if ink is not None:
|
||||
self.draw.draw_chord(xy, start, end, ink, 0)
|
||||
|
||||
##
|
||||
# Draw an ellipse.
|
||||
|
||||
def ellipse(self, xy, fill=None, outline=None):
|
||||
"""Draw an ellipse."""
|
||||
ink, fill = self._getink(outline, fill)
|
||||
if fill is not None:
|
||||
self.draw.draw_ellipse(xy, fill, 1)
|
||||
if ink is not None:
|
||||
self.draw.draw_ellipse(xy, ink, 0)
|
||||
|
||||
##
|
||||
# Draw a line, or a connected sequence of line segments.
|
||||
|
||||
def line(self, xy, fill=None, width=0):
|
||||
"""Draw a line, or a connected sequence of line segments."""
|
||||
ink, fill = self._getink(fill)
|
||||
if ink is not None:
|
||||
self.draw.draw_lines(xy, ink, width)
|
||||
|
||||
##
|
||||
# (Experimental) Draw a shape.
|
||||
|
||||
def shape(self, shape, fill=None, outline=None):
|
||||
# experimental
|
||||
"""(Experimental) Draw a shape."""
|
||||
shape.close()
|
||||
ink, fill = self._getink(outline, fill)
|
||||
if fill is not None:
|
||||
|
@ -190,61 +164,52 @@ class ImageDraw(object):
|
|||
if ink is not None:
|
||||
self.draw.draw_outline(shape, ink, 0)
|
||||
|
||||
##
|
||||
# Draw a pieslice.
|
||||
|
||||
def pieslice(self, xy, start, end, fill=None, outline=None):
|
||||
"""Draw a pieslice."""
|
||||
ink, fill = self._getink(outline, fill)
|
||||
if fill is not None:
|
||||
self.draw.draw_pieslice(xy, start, end, fill, 1)
|
||||
if ink is not None:
|
||||
self.draw.draw_pieslice(xy, start, end, ink, 0)
|
||||
|
||||
##
|
||||
# Draw one or more individual pixels.
|
||||
|
||||
def point(self, xy, fill=None):
|
||||
"""Draw one or more individual pixels."""
|
||||
ink, fill = self._getink(fill)
|
||||
if ink is not None:
|
||||
self.draw.draw_points(xy, ink)
|
||||
|
||||
##
|
||||
# Draw a polygon.
|
||||
|
||||
def polygon(self, xy, fill=None, outline=None):
|
||||
"""Draw a polygon."""
|
||||
ink, fill = self._getink(outline, fill)
|
||||
if fill is not None:
|
||||
self.draw.draw_polygon(xy, fill, 1)
|
||||
if ink is not None:
|
||||
self.draw.draw_polygon(xy, ink, 0)
|
||||
|
||||
##
|
||||
# Draw a rectangle.
|
||||
|
||||
def rectangle(self, xy, fill=None, outline=None):
|
||||
"""Draw a rectangle."""
|
||||
ink, fill = self._getink(outline, fill)
|
||||
if fill is not None:
|
||||
self.draw.draw_rectangle(xy, fill, 1)
|
||||
if ink is not None:
|
||||
self.draw.draw_rectangle(xy, ink, 0)
|
||||
|
||||
##
|
||||
# Draw text.
|
||||
|
||||
def _multiline_check(self, text):
|
||||
split_character = "\n" if isinstance(text, type("")) else b"\n"
|
||||
"""Draw text."""
|
||||
split_character = "\n" if isinstance(text, str) else b"\n"
|
||||
|
||||
return split_character in text
|
||||
|
||||
def _multiline_split(self, text):
|
||||
split_character = "\n" if isinstance(text, type("")) else b"\n"
|
||||
split_character = "\n" if isinstance(text, str) else b"\n"
|
||||
|
||||
return text.split(split_character)
|
||||
|
||||
def text(self, xy, text, fill=None, font=None, anchor=None):
|
||||
def text(self, xy, text, fill=None, font=None, anchor=None,
|
||||
*args, **kwargs):
|
||||
if self._multiline_check(text):
|
||||
return self.multiline_text(xy, text, fill, font, anchor)
|
||||
|
||||
return self.multiline_text(xy, text, fill, font, anchor,
|
||||
*args, **kwargs)
|
||||
ink, fill = self._getink(fill)
|
||||
if font is None:
|
||||
font = self.getfont()
|
||||
|
@ -252,17 +217,17 @@ class ImageDraw(object):
|
|||
ink = fill
|
||||
if ink is not None:
|
||||
try:
|
||||
mask, offset = font.getmask2(text, self.fontmode)
|
||||
mask, offset = font.getmask2(text, self.fontmode, *args, **kwargs)
|
||||
xy = xy[0] + offset[0], xy[1] + offset[1]
|
||||
except AttributeError:
|
||||
try:
|
||||
mask = font.getmask(text, self.fontmode)
|
||||
mask = font.getmask(text, self.fontmode, *args, **kwargs)
|
||||
except TypeError:
|
||||
mask = font.getmask(text)
|
||||
self.draw.draw_bitmap(xy, mask, ink)
|
||||
|
||||
def multiline_text(self, xy, text, fill=None, font=None, anchor=None,
|
||||
spacing=4, align="left"):
|
||||
spacing=4, align="left", direction=None, features=None):
|
||||
widths = []
|
||||
max_width = 0
|
||||
lines = self._multiline_split(text)
|
||||
|
@ -281,47 +246,51 @@ class ImageDraw(object):
|
|||
left += (max_width - widths[idx])
|
||||
else:
|
||||
assert False, 'align must be "left", "center" or "right"'
|
||||
self.text((left, top), line, fill, font, anchor)
|
||||
self.text((left, top), line, fill, font, anchor,
|
||||
direction=direction, features=features)
|
||||
top += line_spacing
|
||||
left = xy[0]
|
||||
|
||||
##
|
||||
# Get the size of a given string, in pixels.
|
||||
|
||||
def textsize(self, text, font=None):
|
||||
def textsize(self, text, font=None, spacing=4, direction=None,
|
||||
features=None):
|
||||
"""Get the size of a given string, in pixels."""
|
||||
if self._multiline_check(text):
|
||||
return self.multiline_textsize(text, font)
|
||||
return self.multiline_textsize(text, font, spacing,
|
||||
direction, features)
|
||||
|
||||
if font is None:
|
||||
font = self.getfont()
|
||||
return font.getsize(text)
|
||||
return font.getsize(text, direction, features)
|
||||
|
||||
def multiline_textsize(self, text, font=None, spacing=4):
|
||||
def multiline_textsize(self, text, font=None, spacing=4, direction=None,
|
||||
features=None):
|
||||
max_width = 0
|
||||
lines = self._multiline_split(text)
|
||||
line_spacing = self.textsize('A', font=font)[1] + spacing
|
||||
for line in lines:
|
||||
line_width, line_height = self.textsize(line, font)
|
||||
line_width, line_height = self.textsize(line, font, spacing,
|
||||
direction, features)
|
||||
max_width = max(max_width, line_width)
|
||||
return max_width, len(lines)*line_spacing
|
||||
return max_width, len(lines)*line_spacing - spacing
|
||||
|
||||
|
||||
##
|
||||
# A simple 2D drawing interface for PIL images.
|
||||
#
|
||||
# @param im The image to draw in.
|
||||
# @param mode Optional mode to use for color values. For RGB
|
||||
# images, this argument can be RGB or RGBA (to blend the
|
||||
# drawing into the image). For all other modes, this argument
|
||||
# must be the same as the image mode. If omitted, the mode
|
||||
# defaults to the mode of the image.
|
||||
|
||||
def Draw(im, mode=None):
|
||||
"""
|
||||
A simple 2D drawing interface for PIL images.
|
||||
|
||||
:param im: The image to draw in.
|
||||
:param mode: Optional mode to use for color values. For RGB
|
||||
images, this argument can be RGB or RGBA (to blend the
|
||||
drawing into the image). For all other modes, this argument
|
||||
must be the same as the image mode. If omitted, the mode
|
||||
defaults to the mode of the image.
|
||||
"""
|
||||
try:
|
||||
return im.getdraw(mode)
|
||||
except AttributeError:
|
||||
return ImageDraw(im, mode)
|
||||
|
||||
|
||||
# experimental access to the outline API
|
||||
try:
|
||||
Outline = Image.core.outline
|
||||
|
@ -329,52 +298,56 @@ except AttributeError:
|
|||
Outline = None
|
||||
|
||||
|
||||
##
|
||||
# (Experimental) A more advanced 2D drawing interface for PIL images,
|
||||
# based on the WCK interface.
|
||||
#
|
||||
# @param im The image to draw in.
|
||||
# @param hints An optional list of hints.
|
||||
# @return A (drawing context, drawing resource factory) tuple.
|
||||
|
||||
def getdraw(im=None, hints=None):
|
||||
"""
|
||||
(Experimental) A more advanced 2D drawing interface for PIL images,
|
||||
based on the WCK interface.
|
||||
|
||||
:param im: The image to draw in.
|
||||
:param hints: An optional list of hints.
|
||||
:returns: A (drawing context, drawing resource factory) tuple.
|
||||
"""
|
||||
# FIXME: this needs more work!
|
||||
# FIXME: come up with a better 'hints' scheme.
|
||||
handler = None
|
||||
if not hints or "nicest" in hints:
|
||||
try:
|
||||
from PIL import _imagingagg as handler
|
||||
from . import _imagingagg as handler
|
||||
except ImportError:
|
||||
pass
|
||||
if handler is None:
|
||||
from PIL import ImageDraw2 as handler
|
||||
from . import ImageDraw2 as handler
|
||||
if im:
|
||||
im = handler.Draw(im)
|
||||
return im, handler
|
||||
|
||||
|
||||
##
|
||||
# (experimental) Fills a bounded region with a given color.
|
||||
#
|
||||
# @param image Target image.
|
||||
# @param xy Seed position (a 2-item coordinate tuple).
|
||||
# @param value Fill color.
|
||||
# @param border Optional border value. If given, the region consists of
|
||||
# pixels with a color different from the border color. If not given,
|
||||
# the region consists of pixels having the same color as the seed
|
||||
# pixel.
|
||||
def floodfill(image, xy, value, border=None, thresh=0):
|
||||
"""
|
||||
(experimental) Fills a bounded region with a given color.
|
||||
|
||||
def floodfill(image, xy, value, border=None):
|
||||
"Fill bounded region."
|
||||
:param image: Target image.
|
||||
:param xy: Seed position (a 2-item coordinate tuple). See
|
||||
:ref:`coordinate-system`.
|
||||
:param value: Fill color.
|
||||
:param border: Optional border value. If given, the region consists of
|
||||
pixels with a color different from the border color. If not given,
|
||||
the region consists of pixels having the same color as the seed
|
||||
pixel.
|
||||
:param thresh: Optional threshold value which specifies a maximum
|
||||
tolerable difference of a pixel value from the 'background' in
|
||||
order for it to be replaced. Useful for filling regions of non-
|
||||
homogeneous, but similar, colors.
|
||||
"""
|
||||
# based on an implementation by Eric S. Raymond
|
||||
pixel = image.load()
|
||||
x, y = xy
|
||||
try:
|
||||
background = pixel[x, y]
|
||||
if background == value:
|
||||
if _color_diff(value, background) <= thresh:
|
||||
return # seed point already has fill color
|
||||
pixel[x, y] = value
|
||||
except IndexError:
|
||||
except (ValueError, IndexError):
|
||||
return # seed point outside image
|
||||
edge = [(x, y)]
|
||||
if border is None:
|
||||
|
@ -387,7 +360,7 @@ def floodfill(image, xy, value, border=None):
|
|||
except IndexError:
|
||||
pass
|
||||
else:
|
||||
if p == background:
|
||||
if _color_diff(p, background) <= thresh:
|
||||
pixel[s, t] = value
|
||||
newedge.append((s, t))
|
||||
edge = newedge
|
||||
|
@ -405,3 +378,10 @@ def floodfill(image, xy, value, border=None):
|
|||
pixel[s, t] = value
|
||||
newedge.append((s, t))
|
||||
edge = newedge
|
||||
|
||||
|
||||
def _color_diff(rgb1, rgb2):
|
||||
"""
|
||||
Uses 1-norm distance to calculate difference between two rgb values.
|
||||
"""
|
||||
return abs(rgb1[0]-rgb2[0]) + abs(rgb1[1]-rgb2[1]) + abs(rgb1[2]-rgb2[2])
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from PIL import Image, ImageColor, ImageDraw, ImageFont, ImagePath
|
||||
from . import Image, ImageColor, ImageDraw, ImageFont, ImagePath
|
||||
|
||||
|
||||
class Pen(object):
|
||||
|
@ -98,9 +98,6 @@ class Draw(object):
|
|||
def rectangle(self, xy, *options):
|
||||
self.render("rectangle", xy, *options)
|
||||
|
||||
def symbol(self, xy, symbol, *options):
|
||||
raise NotImplementedError("not in this version")
|
||||
|
||||
def text(self, xy, text, font):
|
||||
if self.transform:
|
||||
xy = ImagePath.Path(xy)
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from PIL import Image, ImageFilter, ImageStat
|
||||
from . import Image, ImageFilter, ImageStat
|
||||
|
||||
|
||||
class _Enhance(object):
|
||||
|
@ -67,7 +67,7 @@ class Contrast(_Enhance):
|
|||
self.degenerate = Image.new("L", image.size, mean).convert(image.mode)
|
||||
|
||||
if 'A' in image.getbands():
|
||||
self.degenerate.putalpha(image.split()[-1])
|
||||
self.degenerate.putalpha(image.getchannel('A'))
|
||||
|
||||
|
||||
class Brightness(_Enhance):
|
||||
|
@ -82,7 +82,7 @@ class Brightness(_Enhance):
|
|||
self.degenerate = Image.new(image.mode, image.size, 0)
|
||||
|
||||
if 'A' in image.getbands():
|
||||
self.degenerate.putalpha(image.split()[-1])
|
||||
self.degenerate.putalpha(image.getchannel('A'))
|
||||
|
||||
|
||||
class Sharpness(_Enhance):
|
||||
|
@ -97,4 +97,4 @@ class Sharpness(_Enhance):
|
|||
self.degenerate = image.filter(ImageFilter.SMOOTH)
|
||||
|
||||
if 'A' in image.getbands():
|
||||
self.degenerate.putalpha(image.split()[-1])
|
||||
self.degenerate.putalpha(image.getchannel('A'))
|
||||
|
|
|
@ -27,8 +27,8 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from PIL import Image
|
||||
from PIL._util import isPath
|
||||
from . import Image
|
||||
from ._util import isPath
|
||||
import io
|
||||
import os
|
||||
import sys
|
||||
|
@ -78,6 +78,8 @@ class ImageFile(Image.Image):
|
|||
def __init__(self, fp=None, filename=None):
|
||||
Image.Image.__init__(self)
|
||||
|
||||
self._min_frame = 0
|
||||
|
||||
self.tile = None
|
||||
self.readonly = 1 # until we know better
|
||||
|
||||
|
@ -88,10 +90,13 @@ class ImageFile(Image.Image):
|
|||
# filename
|
||||
self.fp = open(fp, "rb")
|
||||
self.filename = fp
|
||||
self._exclusive_fp = True
|
||||
else:
|
||||
# stream
|
||||
self.fp = fp
|
||||
self.filename = filename
|
||||
# can be overridden
|
||||
self._exclusive_fp = None
|
||||
|
||||
try:
|
||||
self._open()
|
||||
|
@ -100,6 +105,9 @@ class ImageFile(Image.Image):
|
|||
KeyError, # unsupported mode
|
||||
EOFError, # got header but not the first frame
|
||||
struct.error) as v:
|
||||
# close the file only if we have opened it this constructor
|
||||
if self._exclusive_fp:
|
||||
self.fp.close()
|
||||
raise SyntaxError(v)
|
||||
|
||||
if not self.mode or self.size[0] <= 0:
|
||||
|
@ -110,11 +118,18 @@ class ImageFile(Image.Image):
|
|||
|
||||
pass
|
||||
|
||||
def get_format_mimetype(self):
|
||||
if self.format is None:
|
||||
return
|
||||
return Image.MIME.get(self.format.upper())
|
||||
|
||||
def verify(self):
|
||||
"Check file integrity"
|
||||
|
||||
# raise exception if something's wrong. must be called
|
||||
# directly after open, and closes file when finished.
|
||||
if self._exclusive_fp:
|
||||
self.fp.close()
|
||||
self.fp = None
|
||||
|
||||
def load(self):
|
||||
|
@ -150,32 +165,34 @@ class ImageFile(Image.Image):
|
|||
|
||||
if use_mmap:
|
||||
# try memory mapping
|
||||
d, e, o, a = self.tile[0]
|
||||
if d == "raw" and a[0] == self.mode and a[0] in Image._MAPMODES:
|
||||
decoder_name, extents, offset, args = self.tile[0]
|
||||
if decoder_name == "raw" and len(args) >= 3 and args[0] == self.mode \
|
||||
and args[0] in Image._MAPMODES:
|
||||
try:
|
||||
if hasattr(Image.core, "map"):
|
||||
# use built-in mapper
|
||||
# use built-in mapper WIN32 only
|
||||
self.map = Image.core.map(self.filename)
|
||||
self.map.seek(o)
|
||||
self.map.seek(offset)
|
||||
self.im = self.map.readimage(
|
||||
self.mode, self.size, a[1], a[2]
|
||||
self.mode, self.size, args[1], args[2]
|
||||
)
|
||||
else:
|
||||
# use mmap, if possible
|
||||
import mmap
|
||||
fp = open(self.filename, "r+")
|
||||
size = os.path.getsize(self.filename)
|
||||
# FIXME: on Unix, use PROT_READ etc
|
||||
self.map = mmap.mmap(fp.fileno(), size)
|
||||
with open(self.filename, "r") as fp:
|
||||
self.map = mmap.mmap(fp.fileno(), 0, access=mmap.ACCESS_READ)
|
||||
self.im = Image.core.map_buffer(
|
||||
self.map, self.size, d, e, o, a
|
||||
self.map, self.size, decoder_name, extents, offset, args
|
||||
)
|
||||
readonly = 1
|
||||
# After trashing self.im, we might need to reload the palette data.
|
||||
if self.palette:
|
||||
self.palette.dirty = 1
|
||||
except (AttributeError, EnvironmentError, ImportError):
|
||||
self.map = None
|
||||
|
||||
self.load_prepare()
|
||||
|
||||
err_code = -3 # initialize to unknown error
|
||||
if not self.map:
|
||||
# sort tiles in file order
|
||||
self.tile.sort(key=_tilesort)
|
||||
|
@ -186,13 +203,16 @@ class ImageFile(Image.Image):
|
|||
except AttributeError:
|
||||
prefix = b""
|
||||
|
||||
for d, e, o, a in self.tile:
|
||||
d = Image._getdecoder(self.mode, d, a, self.decoderconfig)
|
||||
seek(o)
|
||||
for decoder_name, extents, offset, args in self.tile:
|
||||
decoder = Image._getdecoder(self.mode, decoder_name,
|
||||
args, self.decoderconfig)
|
||||
try:
|
||||
d.setimage(self.im, e)
|
||||
except ValueError:
|
||||
continue
|
||||
seek(offset)
|
||||
decoder.setimage(self.im, extents)
|
||||
if decoder.pulls_fd:
|
||||
decoder.setfd(self.fp)
|
||||
status, err_code = decoder.decode(b"")
|
||||
else:
|
||||
b = prefix
|
||||
while True:
|
||||
try:
|
||||
|
@ -203,45 +223,36 @@ class ImageFile(Image.Image):
|
|||
else:
|
||||
raise IOError("image file is truncated")
|
||||
|
||||
if not s and not d.handles_eof: # truncated jpeg
|
||||
self.tile = []
|
||||
|
||||
# JpegDecode needs to clean things up here either way
|
||||
# If we don't destroy the decompressor,
|
||||
# we have a memory leak.
|
||||
d.cleanup()
|
||||
|
||||
if not s: # truncated jpeg
|
||||
if LOAD_TRUNCATED_IMAGES:
|
||||
break
|
||||
else:
|
||||
self.tile = []
|
||||
raise IOError("image file is truncated "
|
||||
"(%d bytes not processed)" % len(b))
|
||||
|
||||
b = b + s
|
||||
n, e = d.decode(b)
|
||||
n, err_code = decoder.decode(b)
|
||||
if n < 0:
|
||||
break
|
||||
b = b[n:]
|
||||
# Need to cleanup here to prevent leaks in PyPy
|
||||
d.cleanup()
|
||||
finally:
|
||||
# Need to cleanup here to prevent leaks
|
||||
decoder.cleanup()
|
||||
|
||||
self.tile = []
|
||||
self.readonly = readonly
|
||||
|
||||
self.fp = None # might be shared
|
||||
|
||||
if not self.map and not LOAD_TRUNCATED_IMAGES and e < 0:
|
||||
# still raised if decoder fails to return anything
|
||||
raise_ioerror(e)
|
||||
|
||||
# post processing
|
||||
if hasattr(self, "tile_post_rotate"):
|
||||
# FIXME: This is a hack to handle rotated PCD's
|
||||
self.im = self.im.rotate(self.tile_post_rotate)
|
||||
self.size = self.im.size
|
||||
|
||||
self.load_end()
|
||||
|
||||
if self._exclusive_fp and self._close_exclusive_fp_after_loading:
|
||||
self.fp.close()
|
||||
self.fp = None
|
||||
|
||||
if not self.map and not LOAD_TRUNCATED_IMAGES and err_code < 0:
|
||||
# still raised if decoder fails to return anything
|
||||
raise_ioerror(err_code)
|
||||
|
||||
return Image.Image.load(self)
|
||||
|
||||
def load_prepare(self):
|
||||
|
@ -265,6 +276,16 @@ class ImageFile(Image.Image):
|
|||
# def load_read(self, bytes):
|
||||
# pass
|
||||
|
||||
def _seek_check(self, frame):
|
||||
if (frame < self._min_frame or
|
||||
# Only check upper limit on frames if additional seek operations
|
||||
# are not required to do so
|
||||
(not (hasattr(self, "_n_frames") and self._n_frames is None) and
|
||||
frame >= self.n_frames+self._min_frame)):
|
||||
raise EOFError("attempt to seek outside sequence")
|
||||
|
||||
return self.tell() != frame
|
||||
|
||||
|
||||
class StubImageFile(ImageFile):
|
||||
"""
|
||||
|
@ -300,8 +321,6 @@ class Parser(object):
|
|||
"""
|
||||
Incremental image parser. This class implements the standard
|
||||
feed/close consumer interface.
|
||||
|
||||
In Python 2.x, this is an old-style class.
|
||||
"""
|
||||
incremental = None
|
||||
image = None
|
||||
|
@ -372,11 +391,8 @@ class Parser(object):
|
|||
|
||||
# attempt to open this file
|
||||
try:
|
||||
try:
|
||||
fp = io.BytesIO(self.data)
|
||||
with io.BytesIO(self.data) as fp:
|
||||
im = Image.open(fp)
|
||||
finally:
|
||||
fp.close() # explicitly close the virtual file
|
||||
except IOError:
|
||||
# traceback.print_exc()
|
||||
pass # not enough data
|
||||
|
@ -403,6 +419,12 @@ class Parser(object):
|
|||
|
||||
self.image = im
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, *args):
|
||||
self.close()
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
(Consumer) Close the stream.
|
||||
|
@ -424,12 +446,11 @@ class Parser(object):
|
|||
if self.data:
|
||||
# incremental parsing not possible; reopen the file
|
||||
# not that we have all data
|
||||
with io.BytesIO(self.data) as fp:
|
||||
try:
|
||||
fp = io.BytesIO(self.data)
|
||||
self.image = Image.open(fp)
|
||||
finally:
|
||||
self.image.load()
|
||||
fp.close() # explicitly close the virtual file
|
||||
return self.image
|
||||
|
||||
|
||||
|
@ -466,6 +487,10 @@ def _save(im, fp, tile, bufsize=0):
|
|||
if o > 0:
|
||||
fp.seek(o, 0)
|
||||
e.setimage(im.im, b)
|
||||
if e.pushes_fd:
|
||||
e.setfd(fp)
|
||||
l, s = e.encode_to_pyfd()
|
||||
else:
|
||||
while True:
|
||||
l, s, d = e.encode(bufsize)
|
||||
fp.write(d)
|
||||
|
@ -481,6 +506,10 @@ def _save(im, fp, tile, bufsize=0):
|
|||
if o > 0:
|
||||
fp.seek(o, 0)
|
||||
e.setimage(im.im, b)
|
||||
if e.pushes_fd:
|
||||
e.setfd(fp)
|
||||
l, s = e.encode_to_pyfd()
|
||||
else:
|
||||
s = e.encode_to_file(fh, bufsize)
|
||||
if s < 0:
|
||||
raise IOError("encoder error %d when writing image file" % s)
|
||||
|
@ -511,3 +540,128 @@ def _safe_read(fp, size):
|
|||
data.append(block)
|
||||
size -= len(block)
|
||||
return b"".join(data)
|
||||
|
||||
|
||||
class PyCodecState(object):
|
||||
def __init__(self):
|
||||
self.xsize = 0
|
||||
self.ysize = 0
|
||||
self.xoff = 0
|
||||
self.yoff = 0
|
||||
|
||||
def extents(self):
|
||||
return (self.xoff, self.yoff,
|
||||
self.xoff+self.xsize, self.yoff+self.ysize)
|
||||
|
||||
|
||||
class PyDecoder(object):
|
||||
"""
|
||||
Python implementation of a format decoder. Override this class and
|
||||
add the decoding logic in the `decode` method.
|
||||
|
||||
See :ref:`Writing Your Own File Decoder in Python<file-decoders-py>`
|
||||
"""
|
||||
|
||||
_pulls_fd = False
|
||||
|
||||
def __init__(self, mode, *args):
|
||||
self.im = None
|
||||
self.state = PyCodecState()
|
||||
self.fd = None
|
||||
self.mode = mode
|
||||
self.init(args)
|
||||
|
||||
def init(self, args):
|
||||
"""
|
||||
Override to perform decoder specific initialization
|
||||
|
||||
:param args: Array of args items from the tile entry
|
||||
:returns: None
|
||||
"""
|
||||
self.args = args
|
||||
|
||||
@property
|
||||
def pulls_fd(self):
|
||||
return self._pulls_fd
|
||||
|
||||
def decode(self, buffer):
|
||||
"""
|
||||
Override to perform the decoding process.
|
||||
|
||||
:param buffer: A bytes object with the data to be decoded. If `handles_eof`
|
||||
is set, then `buffer` will be empty and `self.fd` will be set.
|
||||
:returns: A tuple of (bytes consumed, errcode). If finished with decoding
|
||||
return <0 for the bytes consumed. Err codes are from `ERRORS`
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def cleanup(self):
|
||||
"""
|
||||
Override to perform decoder specific cleanup
|
||||
|
||||
:returns: None
|
||||
"""
|
||||
pass
|
||||
|
||||
def setfd(self, fd):
|
||||
"""
|
||||
Called from ImageFile to set the python file-like object
|
||||
|
||||
:param fd: A python file-like object
|
||||
:returns: None
|
||||
"""
|
||||
self.fd = fd
|
||||
|
||||
def setimage(self, im, extents=None):
|
||||
"""
|
||||
Called from ImageFile to set the core output image for the decoder
|
||||
|
||||
:param im: A core image object
|
||||
:param extents: a 4 tuple of (x0, y0, x1, y1) defining the rectangle
|
||||
for this tile
|
||||
:returns: None
|
||||
"""
|
||||
|
||||
# following c code
|
||||
self.im = im
|
||||
|
||||
if extents:
|
||||
(x0, y0, x1, y1) = extents
|
||||
else:
|
||||
(x0, y0, x1, y1) = (0, 0, 0, 0)
|
||||
|
||||
if x0 == 0 and x1 == 0:
|
||||
self.state.xsize, self.state.ysize = self.im.size
|
||||
else:
|
||||
self.state.xoff = x0
|
||||
self.state.yoff = y0
|
||||
self.state.xsize = x1 - x0
|
||||
self.state.ysize = y1 - y0
|
||||
|
||||
if self.state.xsize <= 0 or self.state.ysize <= 0:
|
||||
raise ValueError("Size cannot be negative")
|
||||
|
||||
if (self.state.xsize + self.state.xoff > self.im.size[0] or
|
||||
self.state.ysize + self.state.yoff > self.im.size[1]):
|
||||
raise ValueError("Tile cannot extend outside image")
|
||||
|
||||
def set_as_raw(self, data, rawmode=None):
|
||||
"""
|
||||
Convenience method to set the internal image from a stream of raw data
|
||||
|
||||
:param data: Bytes to be set
|
||||
:param rawmode: The rawmode to be used for the decoder. If not specified,
|
||||
it will default to the mode of the image
|
||||
:returns: None
|
||||
"""
|
||||
|
||||
if not rawmode:
|
||||
rawmode = self.mode
|
||||
d = Image._getdecoder(self.mode, 'raw', (rawmode))
|
||||
d.setimage(self.im, self.state.extents())
|
||||
s = d.decode(data)
|
||||
|
||||
if s[0] >= 0:
|
||||
raise ValueError("not enough image data")
|
||||
if s[1] != 0:
|
||||
raise ValueError("cannot decode image data")
|
||||
|
|
|
@ -15,14 +15,25 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from __future__ import division
|
||||
|
||||
import functools
|
||||
|
||||
try:
|
||||
import numpy
|
||||
except ImportError: # pragma: no cover
|
||||
numpy = None
|
||||
|
||||
|
||||
class Filter(object):
|
||||
pass
|
||||
|
||||
|
||||
class Kernel(Filter):
|
||||
class MultibandFilter(Filter):
|
||||
pass
|
||||
|
||||
|
||||
class Kernel(MultibandFilter):
|
||||
"""
|
||||
Create a convolution kernel. The current version only
|
||||
supports 3x3 and 5x5 integer and floating point kernels.
|
||||
|
@ -39,6 +50,7 @@ class Kernel(Filter):
|
|||
:param offset: Offset. If given, this value is added to the result,
|
||||
after it has been divided by the scale factor.
|
||||
"""
|
||||
name = "Kernel"
|
||||
|
||||
def __init__(self, size, kernel, scale=None, offset=0):
|
||||
if scale is None:
|
||||
|
@ -126,7 +138,6 @@ class MaxFilter(RankFilter):
|
|||
|
||||
class ModeFilter(Filter):
|
||||
"""
|
||||
|
||||
Create a mode filter. Picks the most frequent pixel value in a box with the
|
||||
given size. Pixel values that occur only once or twice are ignored; if no
|
||||
pixel value occurs more than twice, the original pixel value is preserved.
|
||||
|
@ -142,7 +153,7 @@ class ModeFilter(Filter):
|
|||
return image.modefilter(self.size)
|
||||
|
||||
|
||||
class GaussianBlur(Filter):
|
||||
class GaussianBlur(MultibandFilter):
|
||||
"""Gaussian blur filter.
|
||||
|
||||
:param radius: Blur radius.
|
||||
|
@ -156,7 +167,27 @@ class GaussianBlur(Filter):
|
|||
return image.gaussian_blur(self.radius)
|
||||
|
||||
|
||||
class UnsharpMask(Filter):
|
||||
class BoxBlur(MultibandFilter):
|
||||
"""Blurs the image by setting each pixel to the average value of the pixels
|
||||
in a square box extending radius pixels in each direction.
|
||||
Supports float radius of arbitrary size. Uses an optimized implementation
|
||||
which runs in linear time relative to the size of the image
|
||||
for any radius value.
|
||||
|
||||
:param radius: Size of the box in one direction. Radius 0 does not blur,
|
||||
returns an identical image. Radius 1 takes 1 pixel
|
||||
in each direction, i.e. 9 pixels in total.
|
||||
"""
|
||||
name = "BoxBlur"
|
||||
|
||||
def __init__(self, radius):
|
||||
self.radius = radius
|
||||
|
||||
def filter(self, image):
|
||||
return image.box_blur(self.radius)
|
||||
|
||||
|
||||
class UnsharpMask(MultibandFilter):
|
||||
"""Unsharp mask filter.
|
||||
|
||||
See Wikipedia's entry on `digital unsharp masking`_ for an explanation of
|
||||
|
@ -246,6 +277,15 @@ class FIND_EDGES(BuiltinFilter):
|
|||
)
|
||||
|
||||
|
||||
class SHARPEN(BuiltinFilter):
|
||||
name = "Sharpen"
|
||||
filterargs = (3, 3), 16, 0, (
|
||||
-2, -2, -2,
|
||||
-2, 32, -2,
|
||||
-2, -2, -2
|
||||
)
|
||||
|
||||
|
||||
class SMOOTH(BuiltinFilter):
|
||||
name = "Smooth"
|
||||
filterargs = (3, 3), 13, 0, (
|
||||
|
@ -266,10 +306,181 @@ class SMOOTH_MORE(BuiltinFilter):
|
|||
)
|
||||
|
||||
|
||||
class SHARPEN(BuiltinFilter):
|
||||
name = "Sharpen"
|
||||
filterargs = (3, 3), 16, 0, (
|
||||
-2, -2, -2,
|
||||
-2, 32, -2,
|
||||
-2, -2, -2
|
||||
)
|
||||
class Color3DLUT(MultibandFilter):
|
||||
"""Three-dimensional color lookup table.
|
||||
|
||||
Transforms 3-channel pixels using the values of the channels as coordinates
|
||||
in the 3D lookup table and interpolating the nearest elements.
|
||||
|
||||
This method allows you to apply almost any color transformation
|
||||
in constant time by using pre-calculated decimated tables.
|
||||
|
||||
.. versionadded:: 5.2.0
|
||||
|
||||
:param size: Size of the table. One int or tuple of (int, int, int).
|
||||
Minimal size in any dimension is 2, maximum is 65.
|
||||
:param table: Flat lookup table. A list of ``channels * size**3``
|
||||
float elements or a list of ``size**3`` channels-sized
|
||||
tuples with floats. Channels are changed first,
|
||||
then first dimension, then second, then third.
|
||||
Value 0.0 corresponds lowest value of output, 1.0 highest.
|
||||
:param channels: Number of channels in the table. Could be 3 or 4.
|
||||
Default is 3.
|
||||
:param target_mode: A mode for the result image. Should have not less
|
||||
than ``channels`` channels. Default is ``None``,
|
||||
which means that mode wouldn't be changed.
|
||||
"""
|
||||
name = "Color 3D LUT"
|
||||
|
||||
def __init__(self, size, table, channels=3, target_mode=None, **kwargs):
|
||||
if channels not in (3, 4):
|
||||
raise ValueError("Only 3 or 4 output channels are supported")
|
||||
self.size = size = self._check_size(size)
|
||||
self.channels = channels
|
||||
self.mode = target_mode
|
||||
|
||||
# Hidden flag `_copy_table=False` could be used to avoid extra copying
|
||||
# of the table if the table is specially made for the constructor.
|
||||
copy_table = kwargs.get('_copy_table', True)
|
||||
items = size[0] * size[1] * size[2]
|
||||
wrong_size = False
|
||||
|
||||
if numpy and isinstance(table, numpy.ndarray):
|
||||
if copy_table:
|
||||
table = table.copy()
|
||||
|
||||
if table.shape in [(items * channels,), (items, channels),
|
||||
(size[2], size[1], size[0], channels)]:
|
||||
table = table.reshape(items * channels)
|
||||
else:
|
||||
wrong_size = True
|
||||
|
||||
else:
|
||||
if copy_table:
|
||||
table = list(table)
|
||||
|
||||
# Convert to a flat list
|
||||
if table and isinstance(table[0], (list, tuple)):
|
||||
table, raw_table = [], table
|
||||
for pixel in raw_table:
|
||||
if len(pixel) != channels:
|
||||
raise ValueError(
|
||||
"The elements of the table should "
|
||||
"have a length of {}.".format(channels))
|
||||
table.extend(pixel)
|
||||
|
||||
if wrong_size or len(table) != items * channels:
|
||||
raise ValueError(
|
||||
"The table should have either channels * size**3 float items "
|
||||
"or size**3 items of channels-sized tuples with floats. "
|
||||
"Table should be: {}x{}x{}x{}. Actual length: {}".format(
|
||||
channels, size[0], size[1], size[2], len(table)))
|
||||
self.table = table
|
||||
|
||||
@staticmethod
|
||||
def _check_size(size):
|
||||
try:
|
||||
_, _, _ = size
|
||||
except ValueError:
|
||||
raise ValueError("Size should be either an integer or "
|
||||
"a tuple of three integers.")
|
||||
except TypeError:
|
||||
size = (size, size, size)
|
||||
size = [int(x) for x in size]
|
||||
for size1D in size:
|
||||
if not 2 <= size1D <= 65:
|
||||
raise ValueError("Size should be in [2, 65] range.")
|
||||
return size
|
||||
|
||||
@classmethod
|
||||
def generate(cls, size, callback, channels=3, target_mode=None):
|
||||
"""Generates new LUT using provided callback.
|
||||
|
||||
:param size: Size of the table. Passed to the constructor.
|
||||
:param callback: Function with three parameters which correspond
|
||||
three color channels. Will be called ``size**3``
|
||||
times with values from 0.0 to 1.0 and should return
|
||||
a tuple with ``channels`` elements.
|
||||
:param channels: The number of channels which should return callback.
|
||||
:param target_mode: Passed to the constructor of the resulting
|
||||
lookup table.
|
||||
"""
|
||||
size1D, size2D, size3D = cls._check_size(size)
|
||||
if channels not in (3, 4):
|
||||
raise ValueError("Only 3 or 4 output channels are supported")
|
||||
|
||||
table = [0] * (size1D * size2D * size3D * channels)
|
||||
idx_out = 0
|
||||
for b in range(size3D):
|
||||
for g in range(size2D):
|
||||
for r in range(size1D):
|
||||
table[idx_out:idx_out + channels] = callback(
|
||||
r / (size1D-1), g / (size2D-1), b / (size3D-1))
|
||||
idx_out += channels
|
||||
|
||||
return cls((size1D, size2D, size3D), table, channels=channels,
|
||||
target_mode=target_mode, _copy_table=False)
|
||||
|
||||
def transform(self, callback, with_normals=False, channels=None,
|
||||
target_mode=None):
|
||||
"""Transforms the table values using provided callback and returns
|
||||
a new LUT with altered values.
|
||||
|
||||
:param callback: A function which takes old lookup table values
|
||||
and returns a new set of values. The number
|
||||
of arguments which function should take is
|
||||
``self.channels`` or ``3 + self.channels``
|
||||
if ``with_normals`` flag is set.
|
||||
Should return a tuple of ``self.channels`` or
|
||||
``channels`` elements if it is set.
|
||||
:param with_normals: If true, ``callback`` will be called with
|
||||
coordinates in the color cube as the first
|
||||
three arguments. Otherwise, ``callback``
|
||||
will be called only with actual color values.
|
||||
:param channels: The number of channels in the resulting lookup table.
|
||||
:param target_mode: Passed to the constructor of the resulting
|
||||
lookup table.
|
||||
"""
|
||||
if channels not in (None, 3, 4):
|
||||
raise ValueError("Only 3 or 4 output channels are supported")
|
||||
ch_in = self.channels
|
||||
ch_out = channels or ch_in
|
||||
size1D, size2D, size3D = self.size
|
||||
|
||||
table = [0] * (size1D * size2D * size3D * ch_out)
|
||||
idx_in = 0
|
||||
idx_out = 0
|
||||
for b in range(size3D):
|
||||
for g in range(size2D):
|
||||
for r in range(size1D):
|
||||
values = self.table[idx_in:idx_in + ch_in]
|
||||
if with_normals:
|
||||
values = callback(r / (size1D-1), g / (size2D-1),
|
||||
b / (size3D-1), *values)
|
||||
else:
|
||||
values = callback(*values)
|
||||
table[idx_out:idx_out + ch_out] = values
|
||||
idx_in += ch_in
|
||||
idx_out += ch_out
|
||||
|
||||
return type(self)(self.size, table, channels=ch_out,
|
||||
target_mode=target_mode or self.mode,
|
||||
_copy_table=False)
|
||||
|
||||
def __repr__(self):
|
||||
r = [
|
||||
"{} from {}".format(self.__class__.__name__,
|
||||
self.table.__class__.__name__),
|
||||
"size={:d}x{:d}x{:d}".format(*self.size),
|
||||
"channels={:d}".format(self.channels),
|
||||
]
|
||||
if self.mode:
|
||||
r.append("target_mode={}".format(self.mode))
|
||||
return "<{}>".format(" ".join(r))
|
||||
|
||||
def filter(self, image):
|
||||
from . import Image
|
||||
|
||||
return image.color_lut_3d(
|
||||
self.mode or image.mode, Image.LINEAR, self.channels,
|
||||
self.size[0], self.size[1], self.size[2], self.table)
|
||||
|
|
|
@ -25,22 +25,27 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from PIL import Image
|
||||
from PIL._util import isDirectory, isPath
|
||||
from . import Image
|
||||
from ._util import isDirectory, isPath, py3
|
||||
import os
|
||||
import sys
|
||||
|
||||
LAYOUT_BASIC = 0
|
||||
LAYOUT_RAQM = 1
|
||||
|
||||
|
||||
class _imagingft_not_installed(object):
|
||||
# module placeholder
|
||||
def __getattr__(self, id):
|
||||
raise ImportError("The _imagingft C module is not installed")
|
||||
|
||||
|
||||
try:
|
||||
from PIL import _imagingft as core
|
||||
from . import _imagingft as core
|
||||
except ImportError:
|
||||
core = _imagingft_not_installed()
|
||||
|
||||
|
||||
# FIXME: add support for pilfont2 format (see FontFile.py)
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
|
@ -62,8 +67,7 @@ class ImageFont(object):
|
|||
|
||||
def _load_pilfont(self, filename):
|
||||
|
||||
fp = open(filename, "rb")
|
||||
|
||||
with open(filename, "rb") as fp:
|
||||
for ext in (".png", ".gif", ".pbm"):
|
||||
try:
|
||||
fullname = os.path.splitext(filename)[0] + ext
|
||||
|
@ -104,9 +108,11 @@ class ImageFont(object):
|
|||
|
||||
self.font = Image.core.font(image.im, data)
|
||||
|
||||
# delegate critical operations to internal type
|
||||
self.getsize = self.font.getsize
|
||||
self.getmask = self.font.getmask
|
||||
def getsize(self, text, *args, **kwargs):
|
||||
return self.font.getsize(text)
|
||||
|
||||
def getmask(self, text, mode="", *args, **kwargs):
|
||||
return self.font.getmask(text, mode)
|
||||
|
||||
|
||||
##
|
||||
|
@ -116,7 +122,8 @@ class ImageFont(object):
|
|||
class FreeTypeFont(object):
|
||||
"FreeType font wrapper (requires _imagingft service)"
|
||||
|
||||
def __init__(self, font=None, size=10, index=0, encoding=""):
|
||||
def __init__(self, font=None, size=10, index=0, encoding="",
|
||||
layout_engine=None):
|
||||
# FIXME: use service provider instead
|
||||
|
||||
self.path = font
|
||||
|
@ -124,12 +131,25 @@ class FreeTypeFont(object):
|
|||
self.index = index
|
||||
self.encoding = encoding
|
||||
|
||||
if layout_engine not in (LAYOUT_BASIC, LAYOUT_RAQM):
|
||||
layout_engine = LAYOUT_BASIC
|
||||
if core.HAVE_RAQM:
|
||||
layout_engine = LAYOUT_RAQM
|
||||
if layout_engine == LAYOUT_RAQM and not core.HAVE_RAQM:
|
||||
layout_engine = LAYOUT_BASIC
|
||||
|
||||
self.layout_engine = layout_engine
|
||||
|
||||
if isPath(font):
|
||||
self.font = core.getfont(font, size, index, encoding)
|
||||
self.font = core.getfont(font, size, index, encoding, layout_engine=layout_engine)
|
||||
else:
|
||||
self.font_bytes = font.read()
|
||||
self.font = core.getfont(
|
||||
"", size, index, encoding, self.font_bytes)
|
||||
"", size, index, encoding, self.font_bytes, layout_engine)
|
||||
|
||||
def _multiline_split(self, text):
|
||||
split_character = "\n" if isinstance(text, str) else b"\n"
|
||||
return text.split(split_character)
|
||||
|
||||
def getname(self):
|
||||
return self.font.family, self.font.style
|
||||
|
@ -137,23 +157,34 @@ class FreeTypeFont(object):
|
|||
def getmetrics(self):
|
||||
return self.font.ascent, self.font.descent
|
||||
|
||||
def getsize(self, text):
|
||||
size, offset = self.font.getsize(text)
|
||||
def getsize(self, text, direction=None, features=None):
|
||||
size, offset = self.font.getsize(text, direction, features)
|
||||
return (size[0] + offset[0], size[1] + offset[1])
|
||||
|
||||
def getsize_multiline(self, text, direction=None, spacing=4, features=None):
|
||||
max_width = 0
|
||||
lines = self._multiline_split(text)
|
||||
line_spacing = self.getsize('A')[1] + spacing
|
||||
for line in lines:
|
||||
line_width, line_height = self.getsize(line, direction, features)
|
||||
max_width = max(max_width, line_width)
|
||||
|
||||
return max_width, len(lines)*line_spacing - spacing
|
||||
|
||||
def getoffset(self, text):
|
||||
return self.font.getsize(text)[1]
|
||||
|
||||
def getmask(self, text, mode=""):
|
||||
return self.getmask2(text, mode)[0]
|
||||
def getmask(self, text, mode="", direction=None, features=None):
|
||||
return self.getmask2(text, mode, direction=direction, features=features)[0]
|
||||
|
||||
def getmask2(self, text, mode="", fill=Image.core.fill):
|
||||
size, offset = self.font.getsize(text)
|
||||
def getmask2(self, text, mode="", fill=Image.core.fill, direction=None, features=None, *args, **kwargs):
|
||||
size, offset = self.font.getsize(text, direction, features)
|
||||
im = fill("L", size, 0)
|
||||
self.font.render(text, im.id, mode == "1")
|
||||
self.font.render(text, im.id, mode == "1", direction, features)
|
||||
return im, offset
|
||||
|
||||
def font_variant(self, font=None, size=None, index=None, encoding=None):
|
||||
def font_variant(self, font=None, size=None, index=None, encoding=None,
|
||||
layout_engine=None):
|
||||
"""
|
||||
Create a copy of this FreeTypeFont object,
|
||||
using any specified arguments to override the settings.
|
||||
|
@ -166,34 +197,35 @@ class FreeTypeFont(object):
|
|||
return FreeTypeFont(font=self.path if font is None else font,
|
||||
size=self.size if size is None else size,
|
||||
index=self.index if index is None else index,
|
||||
encoding=self.encoding if encoding is None else
|
||||
encoding)
|
||||
|
||||
##
|
||||
# Wrapper that creates a transposed font from any existing font
|
||||
# object.
|
||||
#
|
||||
# @param font A font object.
|
||||
# @param orientation An optional orientation. If given, this should
|
||||
# be one of Image.FLIP_LEFT_RIGHT, Image.FLIP_TOP_BOTTOM,
|
||||
# Image.ROTATE_90, Image.ROTATE_180, or Image.ROTATE_270.
|
||||
encoding=self.encoding if encoding is None else encoding,
|
||||
layout_engine=self.layout_engine if layout_engine is None else layout_engine
|
||||
)
|
||||
|
||||
|
||||
class TransposedFont(object):
|
||||
"Wrapper for writing rotated or mirrored text"
|
||||
|
||||
def __init__(self, font, orientation=None):
|
||||
"""
|
||||
Wrapper that creates a transposed font from any existing font
|
||||
object.
|
||||
|
||||
:param font: A font object.
|
||||
:param orientation: An optional orientation. If given, this should
|
||||
be one of Image.FLIP_LEFT_RIGHT, Image.FLIP_TOP_BOTTOM,
|
||||
Image.ROTATE_90, Image.ROTATE_180, or Image.ROTATE_270.
|
||||
"""
|
||||
self.font = font
|
||||
self.orientation = orientation # any 'transpose' argument, or None
|
||||
|
||||
def getsize(self, text):
|
||||
def getsize(self, text, *args, **kwargs):
|
||||
w, h = self.font.getsize(text)
|
||||
if self.orientation in (Image.ROTATE_90, Image.ROTATE_270):
|
||||
return h, w
|
||||
return w, h
|
||||
|
||||
def getmask(self, text, mode=""):
|
||||
im = self.font.getmask(text, mode)
|
||||
def getmask(self, text, mode="", *args, **kwargs):
|
||||
im = self.font.getmask(text, mode, *args, **kwargs)
|
||||
if self.orientation is not None:
|
||||
return im.transpose(self.orientation)
|
||||
return im
|
||||
|
@ -213,17 +245,19 @@ def load(filename):
|
|||
return f
|
||||
|
||||
|
||||
def truetype(font=None, size=10, index=0, encoding=""):
|
||||
def truetype(font=None, size=10, index=0, encoding="",
|
||||
layout_engine=None):
|
||||
"""
|
||||
Load a TrueType or OpenType font file, and create a font object.
|
||||
This function loads a font object from the given file, and creates
|
||||
a font object for a font of the given size.
|
||||
Load a TrueType or OpenType font from a file or file-like object,
|
||||
and create a font object.
|
||||
This function loads a font object from the given file or file-like
|
||||
object, and creates a font object for a font of the given size.
|
||||
|
||||
This function requires the _imagingft service.
|
||||
|
||||
:param font: A truetype font file. Under Windows, if the file
|
||||
is not found in this filename, the loader also looks in
|
||||
Windows :file:`fonts/` directory.
|
||||
:param font: A filename or file-like object containing a TrueType font.
|
||||
Under Windows, if the file is not found in this filename,
|
||||
the loader also looks in Windows :file:`fonts/` directory.
|
||||
:param size: The requested size, in points.
|
||||
:param index: Which font face to load (default is first available face).
|
||||
:param encoding: Which font encoding to use (default is Unicode). Common
|
||||
|
@ -231,12 +265,14 @@ def truetype(font=None, size=10, index=0, encoding=""):
|
|||
Symbol), "ADOB" (Adobe Standard), "ADBE" (Adobe Expert),
|
||||
and "armn" (Apple Roman). See the FreeType documentation
|
||||
for more information.
|
||||
:param layout_engine: Which layout engine to use, if available:
|
||||
`ImageFont.LAYOUT_BASIC` or `ImageFont.LAYOUT_RAQM`.
|
||||
:return: A font object.
|
||||
:exception IOError: If the file could not be read.
|
||||
"""
|
||||
|
||||
try:
|
||||
return FreeTypeFont(font, size, index, encoding)
|
||||
return FreeTypeFont(font, size, index, encoding, layout_engine)
|
||||
except IOError:
|
||||
ttf_filename = os.path.basename(font)
|
||||
|
||||
|
@ -267,16 +303,16 @@ def truetype(font=None, size=10, index=0, encoding=""):
|
|||
for walkfilename in walkfilenames:
|
||||
if ext and walkfilename == ttf_filename:
|
||||
fontpath = os.path.join(walkroot, walkfilename)
|
||||
return FreeTypeFont(fontpath, size, index, encoding)
|
||||
return FreeTypeFont(fontpath, size, index, encoding, layout_engine)
|
||||
elif not ext and os.path.splitext(walkfilename)[0] == ttf_filename:
|
||||
fontpath = os.path.join(walkroot, walkfilename)
|
||||
if os.path.splitext(fontpath)[1] == '.ttf':
|
||||
return FreeTypeFont(fontpath, size, index, encoding)
|
||||
return FreeTypeFont(fontpath, size, index, encoding, layout_engine)
|
||||
if not ext and first_font_with_a_different_extension is None:
|
||||
first_font_with_a_different_extension = fontpath
|
||||
if first_font_with_a_different_extension:
|
||||
return FreeTypeFont(first_font_with_a_different_extension, size,
|
||||
index, encoding)
|
||||
index, encoding, layout_engine)
|
||||
raise
|
||||
|
||||
|
||||
|
@ -292,10 +328,10 @@ def load_path(filename):
|
|||
for directory in sys.path:
|
||||
if isDirectory(directory):
|
||||
if not isinstance(filename, str):
|
||||
if bytes is str:
|
||||
filename = filename.encode("utf-8")
|
||||
else:
|
||||
if py3:
|
||||
filename = filename.decode("utf-8")
|
||||
else:
|
||||
filename = filename.encode("utf-8")
|
||||
try:
|
||||
return load(os.path.join(directory, filename))
|
||||
except IOError:
|
||||
|
@ -315,7 +351,7 @@ def load_default():
|
|||
f = ImageFont()
|
||||
f._load_pilfont_data(
|
||||
# courB08
|
||||
BytesIO(base64.decodestring(b'''
|
||||
BytesIO(base64.b64decode(b'''
|
||||
UElMZm9udAo7Ozs7OzsxMDsKREFUQQoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
|
@ -407,7 +443,7 @@ AJsAEQAGAAAAAP/6AAX//wCbAAoAoAAPAAYAAAAA//oABQABAKAACgClABEABgAA////+AAGAAAA
|
|||
pQAKAKwAEgAGAAD////4AAYAAACsAAoAswASAAYAAP////gABgAAALMACgC6ABIABgAA////+QAG
|
||||
AAAAugAKAMEAEQAGAAD////4AAYAAgDBAAoAyAAUAAYAAP////kABQACAMgACgDOABMABgAA////
|
||||
+QAGAAIAzgAKANUAEw==
|
||||
''')), Image.open(BytesIO(base64.decodestring(b'''
|
||||
''')), Image.open(BytesIO(base64.b64decode(b'''
|
||||
iVBORw0KGgoAAAANSUhEUgAAAx4AAAAUAQAAAAArMtZoAAAEwElEQVR4nABlAJr/AHVE4czCI/4u
|
||||
Mc4b7vuds/xzjz5/3/7u/n9vMe7vnfH/9++vPn/xyf5zhxzjt8GHw8+2d83u8x27199/nxuQ6Od9
|
||||
M43/5z2I+9n9ZtmDBwMQECDRQw/eQIQohJXxpBCNVE6QCCAAAAD//wBlAJr/AgALyj1t/wINwq0g
|
||||
|
@ -433,5 +469,3 @@ Gc/eeW7BwPj5+QGZhANUswMAAAD//2JgqGBgYGBgqEMXlvhMPUsAAAAA//8iYDd1AAAAAP//AwDR
|
|||
w7IkEbzhVQAAAABJRU5ErkJggg==
|
||||
'''))))
|
||||
return f
|
||||
|
||||
# End of file
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
# The Python Imaging Library
|
||||
# $Id$
|
||||
#
|
||||
# screen grabber (OS X and Windows only)
|
||||
# screen grabber (macOS and Windows only)
|
||||
#
|
||||
# History:
|
||||
# 2001-04-26 fl created
|
||||
|
@ -15,11 +15,11 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from PIL import Image
|
||||
from . import Image
|
||||
|
||||
import sys
|
||||
if sys.platform not in ["win32", "darwin"]:
|
||||
raise ImportError("ImageGrab is OS X and Windows only")
|
||||
raise ImportError("ImageGrab is macOS and Windows only")
|
||||
|
||||
if sys.platform == "win32":
|
||||
grabber = Image.core.grabscreen
|
||||
|
@ -31,17 +31,17 @@ elif sys.platform == "darwin":
|
|||
|
||||
def grab(bbox=None):
|
||||
if sys.platform == "darwin":
|
||||
f, file = tempfile.mkstemp('.png')
|
||||
os.close(f)
|
||||
subprocess.call(['screencapture', '-x', file])
|
||||
im = Image.open(file)
|
||||
fh, filepath = tempfile.mkstemp('.png')
|
||||
os.close(fh)
|
||||
subprocess.call(['screencapture', '-x', filepath])
|
||||
im = Image.open(filepath)
|
||||
im.load()
|
||||
os.unlink(file)
|
||||
os.unlink(filepath)
|
||||
else:
|
||||
size, data = grabber()
|
||||
im = Image.frombytes(
|
||||
"RGB", size, data,
|
||||
# RGB, 32-bit line padding, origo in lower left corner
|
||||
# RGB, 32-bit line padding, origin lower left corner
|
||||
"raw", "BGR", (size[0]*3 + 3) & -4, -1
|
||||
)
|
||||
if bbox:
|
||||
|
@ -51,11 +51,30 @@ def grab(bbox=None):
|
|||
|
||||
def grabclipboard():
|
||||
if sys.platform == "darwin":
|
||||
raise NotImplementedError("Method is not implemented on OS X")
|
||||
debug = 0 # temporary interface
|
||||
data = Image.core.grabclipboard(debug)
|
||||
fh, filepath = tempfile.mkstemp('.jpg')
|
||||
os.close(fh)
|
||||
commands = [
|
||||
"set theFile to (open for access POSIX file \""+filepath+"\" with write permission)",
|
||||
"try",
|
||||
"write (the clipboard as JPEG picture) to theFile",
|
||||
"end try",
|
||||
"close access theFile"
|
||||
]
|
||||
script = ["osascript"]
|
||||
for command in commands:
|
||||
script += ["-e", command]
|
||||
subprocess.call(script)
|
||||
|
||||
im = None
|
||||
if os.stat(filepath).st_size != 0:
|
||||
im = Image.open(filepath)
|
||||
im.load()
|
||||
os.unlink(filepath)
|
||||
return im
|
||||
else:
|
||||
data = Image.core.grabclipboard()
|
||||
if isinstance(data, bytes):
|
||||
from PIL import BmpImagePlugin
|
||||
from . import BmpImagePlugin
|
||||
import io
|
||||
return BmpImagePlugin.DibImageFile(io.BytesIO(data))
|
||||
return data
|
||||
|
|
|
@ -15,8 +15,8 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from PIL import Image
|
||||
from PIL import _imagingmath
|
||||
from . import Image, _imagingmath
|
||||
from ._util import py3
|
||||
|
||||
try:
|
||||
import builtins
|
||||
|
@ -32,7 +32,7 @@ def _isconstant(v):
|
|||
|
||||
|
||||
class _Operand(object):
|
||||
# wraps an image operand, providing standard operators
|
||||
"""Wraps an image operand, providing standard operators"""
|
||||
|
||||
def __init__(self, im):
|
||||
self.im = im
|
||||
|
@ -101,7 +101,7 @@ class _Operand(object):
|
|||
# an image is "true" if it contains at least one non-zero pixel
|
||||
return self.im.getbbox() is not None
|
||||
|
||||
if bytes is str:
|
||||
if not py3:
|
||||
# Provide __nonzero__ for pre-Py3k
|
||||
__nonzero__ = __bool__
|
||||
del __bool__
|
||||
|
@ -152,7 +152,7 @@ class _Operand(object):
|
|||
def __rpow__(self, other):
|
||||
return self.apply("pow", other, self)
|
||||
|
||||
if bytes is str:
|
||||
if not py3:
|
||||
# Provide __div__ and __rdiv__ for pre-Py3k
|
||||
__div__ = __truediv__
|
||||
__rdiv__ = __rtruediv__
|
||||
|
@ -236,6 +236,7 @@ def imagemath_max(self, other):
|
|||
def imagemath_convert(self, mode):
|
||||
return _Operand(self.im.convert(mode))
|
||||
|
||||
|
||||
ops = {}
|
||||
for k, v in list(globals().items()):
|
||||
if k[:10] == "imagemath_":
|
||||
|
|
|
@ -14,13 +14,11 @@
|
|||
#
|
||||
|
||||
# mode descriptor cache
|
||||
_modes = {}
|
||||
_modes = None
|
||||
|
||||
|
||||
##
|
||||
# Wrapper for mode strings.
|
||||
|
||||
class ModeDescriptor(object):
|
||||
"""Wrapper for mode strings."""
|
||||
|
||||
def __init__(self, mode, bands, basemode, basetype):
|
||||
self.mode = mode
|
||||
|
@ -32,21 +30,26 @@ class ModeDescriptor(object):
|
|||
return self.mode
|
||||
|
||||
|
||||
##
|
||||
# Gets a mode descriptor for the given mode.
|
||||
|
||||
def getmode(mode):
|
||||
"""Gets a mode descriptor for the given mode."""
|
||||
global _modes
|
||||
if not _modes:
|
||||
# initialize mode cache
|
||||
from PIL import Image
|
||||
|
||||
from . import Image
|
||||
modes = {}
|
||||
# core modes
|
||||
for m, (basemode, basetype, bands) in Image._MODEINFO.items():
|
||||
_modes[m] = ModeDescriptor(m, bands, basemode, basetype)
|
||||
modes[m] = ModeDescriptor(m, bands, basemode, basetype)
|
||||
# extra experimental modes
|
||||
_modes["LA"] = ModeDescriptor("LA", ("L", "A"), "L", "L")
|
||||
_modes["PA"] = ModeDescriptor("PA", ("P", "A"), "RGB", "L")
|
||||
modes["RGBa"] = ModeDescriptor("RGBa", ("R", "G", "B", "a"), "RGB", "L")
|
||||
modes["LA"] = ModeDescriptor("LA", ("L", "A"), "L", "L")
|
||||
modes["La"] = ModeDescriptor("La", ("L", "a"), "L", "L")
|
||||
modes["PA"] = ModeDescriptor("PA", ("P", "A"), "RGB", "L")
|
||||
# mapping modes
|
||||
_modes["I;16"] = ModeDescriptor("I;16", "I", "L", "L")
|
||||
_modes["I;16L"] = ModeDescriptor("I;16L", "I", "L", "L")
|
||||
_modes["I;16B"] = ModeDescriptor("I;16B", "I", "L", "L")
|
||||
modes["I;16"] = ModeDescriptor("I;16", "I", "L", "L")
|
||||
modes["I;16L"] = ModeDescriptor("I;16L", "I", "L", "L")
|
||||
modes["I;16B"] = ModeDescriptor("I;16B", "I", "L", "L")
|
||||
# set global mode cache atomically
|
||||
_modes = modes
|
||||
return _modes[mode]
|
||||
|
|
|
@ -5,8 +5,9 @@
|
|||
#
|
||||
# Copyright (c) 2014 Dov Grobgeld <dov.grobgeld@gmail.com>
|
||||
|
||||
from PIL import Image
|
||||
from PIL import _imagingmorph
|
||||
from __future__ import print_function
|
||||
|
||||
from . import Image, _imagingmorph
|
||||
import re
|
||||
|
||||
LUT_SIZE = 1 << 9
|
||||
|
@ -78,7 +79,7 @@ class LutBuilder(object):
|
|||
def build_default_lut(self):
|
||||
symbols = [0, 1]
|
||||
m = 1 << 4 # pos of current pixel
|
||||
self.lut = bytearray([symbols[(i & m) > 0] for i in range(LUT_SIZE)])
|
||||
self.lut = bytearray(symbols[(i & m) > 0] for i in range(LUT_SIZE))
|
||||
|
||||
def get_lut(self):
|
||||
return self.lut
|
||||
|
@ -88,7 +89,7 @@ class LutBuilder(object):
|
|||
string permuted according to the permutation list.
|
||||
"""
|
||||
assert(len(permutation) == 9)
|
||||
return ''.join([pattern[p] for p in permutation])
|
||||
return ''.join(pattern[p] for p in permutation)
|
||||
|
||||
def _pattern_permute(self, basic_pattern, options, basic_result):
|
||||
"""pattern_permute takes a basic pattern and its result and clones
|
||||
|
@ -122,7 +123,7 @@ class LutBuilder(object):
|
|||
.replace('0', 'Z')
|
||||
.replace('1', '0')
|
||||
.replace('Z', '1'))
|
||||
res = '%d' % (1-int(res))
|
||||
res = 1-int(res)
|
||||
patterns.append((pattern, res))
|
||||
|
||||
return patterns
|
||||
|
@ -152,14 +153,14 @@ class LutBuilder(object):
|
|||
|
||||
# # Debugging
|
||||
# for p, r in patterns:
|
||||
# print p,r
|
||||
# print '--'
|
||||
# print(p, r)
|
||||
# print('--')
|
||||
|
||||
# compile the patterns into regular expressions for speed
|
||||
for i in range(len(patterns)):
|
||||
p = patterns[i][0].replace('.', 'X').replace('X', '[01]')
|
||||
for i, pattern in enumerate(patterns):
|
||||
p = pattern[0].replace('.', 'X').replace('X', '[01]')
|
||||
p = re.compile(p)
|
||||
patterns[i] = (p, patterns[i][1])
|
||||
patterns[i] = (p, pattern[1])
|
||||
|
||||
# Step through table and find patterns that match.
|
||||
# Note that all the patterns are searched. The last one
|
||||
|
@ -210,7 +211,7 @@ class MorphOp(object):
|
|||
an image.
|
||||
|
||||
Returns a list of tuples of (x,y) coordinates
|
||||
of all matching pixels."""
|
||||
of all matching pixels. See :ref:`coordinate-system`."""
|
||||
if self.lut is None:
|
||||
raise Exception('No operator loaded')
|
||||
|
||||
|
@ -222,7 +223,7 @@ class MorphOp(object):
|
|||
"""Get a list of all turned on pixels in a binary image
|
||||
|
||||
Returns a list of tuples of (x,y) coordinates
|
||||
of all matching pixels."""
|
||||
of all matching pixels. See :ref:`coordinate-system`."""
|
||||
|
||||
if image.mode != 'L':
|
||||
raise Exception('Image must be binary, meaning it must use mode L')
|
||||
|
@ -233,7 +234,7 @@ class MorphOp(object):
|
|||
with open(filename, 'rb') as f:
|
||||
self.lut = bytearray(f.read())
|
||||
|
||||
if len(self.lut) != 8192:
|
||||
if len(self.lut) != LUT_SIZE:
|
||||
self.lut = None
|
||||
raise Exception('Wrong size operator file!')
|
||||
|
||||
|
@ -247,5 +248,3 @@ class MorphOp(object):
|
|||
def set_lut(self, lut):
|
||||
"""Set the lut from an external source"""
|
||||
self.lut = lut
|
||||
|
||||
# End of file
|
||||
|
|
|
@ -17,10 +17,11 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from PIL import Image
|
||||
from PIL._util import isStringType
|
||||
from . import Image
|
||||
from ._util import isStringType
|
||||
import operator
|
||||
import functools
|
||||
import warnings
|
||||
|
||||
|
||||
#
|
||||
|
@ -39,7 +40,7 @@ def _border(border):
|
|||
|
||||
def _color(color, mode):
|
||||
if isStringType(color):
|
||||
from PIL import ImageColor
|
||||
from . import ImageColor
|
||||
color = ImageColor.getcolor(color, mode)
|
||||
return color
|
||||
|
||||
|
@ -178,6 +179,28 @@ def crop(image, border=0):
|
|||
)
|
||||
|
||||
|
||||
def scale(image, factor, resample=Image.NEAREST):
|
||||
"""
|
||||
Returns a rescaled image by a specific factor given in parameter.
|
||||
A factor greater than 1 expands the image, between 0 and 1 contracts the
|
||||
image.
|
||||
|
||||
:param image: The image to rescale.
|
||||
:param factor: The expansion factor, as a float.
|
||||
:param resample: An optional resampling filter. Same values possible as
|
||||
in the PIL.Image.resize function.
|
||||
:returns: An :py:class:`~PIL.Image.Image` object.
|
||||
"""
|
||||
if factor == 1:
|
||||
return image.copy()
|
||||
elif factor <= 0:
|
||||
raise ValueError("the factor must be greater than 0")
|
||||
else:
|
||||
size = (int(round(factor * image.width)),
|
||||
int(round(factor * image.height)))
|
||||
return image.resize(size, resample)
|
||||
|
||||
|
||||
def deform(image, deformer, resample=Image.BILINEAR):
|
||||
"""
|
||||
Deform the image.
|
||||
|
@ -185,7 +208,8 @@ def deform(image, deformer, resample=Image.BILINEAR):
|
|||
:param image: The image to deform.
|
||||
:param deformer: A deformer object. Any object that implements a
|
||||
**getmesh** method can be used.
|
||||
:param resample: What resampling filter to use.
|
||||
:param resample: An optional resampling filter. Same values possible as
|
||||
in the PIL.Image.transform function.
|
||||
:return: An image.
|
||||
"""
|
||||
return image.transform(
|
||||
|
@ -248,6 +272,7 @@ def fit(image, size, method=Image.NEAREST, bleed=0.0, centering=(0.5, 0.5)):
|
|||
|
||||
This function was contributed by Kevin Cazabon.
|
||||
|
||||
:param image: The image to size and crop.
|
||||
:param size: The requested output size in pixels, given as a
|
||||
(width, height) tuple.
|
||||
:param method: What resampling method to use. Default is
|
||||
|
@ -415,6 +440,13 @@ def solarize(image, threshold=128):
|
|||
def gaussian_blur(im, radius=None):
|
||||
""" PIL_usm.gblur(im, [radius])"""
|
||||
|
||||
warnings.warn(
|
||||
'PIL.ImageOps.gaussian_blur is deprecated. '
|
||||
'Use PIL.ImageFilter.GaussianBlur instead. '
|
||||
'This function will be removed in a future version.',
|
||||
DeprecationWarning
|
||||
)
|
||||
|
||||
if radius is None:
|
||||
radius = 5.0
|
||||
|
||||
|
@ -422,12 +454,30 @@ def gaussian_blur(im, radius=None):
|
|||
|
||||
return im.im.gaussian_blur(radius)
|
||||
|
||||
gblur = gaussian_blur
|
||||
|
||||
def gblur(im, radius=None):
|
||||
""" PIL_usm.gblur(im, [radius])"""
|
||||
|
||||
warnings.warn(
|
||||
'PIL.ImageOps.gblur is deprecated. '
|
||||
'Use PIL.ImageFilter.GaussianBlur instead. '
|
||||
'This function will be removed in a future version.',
|
||||
DeprecationWarning
|
||||
)
|
||||
|
||||
return gaussian_blur(im, radius)
|
||||
|
||||
|
||||
def unsharp_mask(im, radius=None, percent=None, threshold=None):
|
||||
""" PIL_usm.usm(im, [radius, percent, threshold])"""
|
||||
|
||||
warnings.warn(
|
||||
'PIL.ImageOps.unsharp_mask is deprecated. '
|
||||
'Use PIL.ImageFilter.UnsharpMask instead. '
|
||||
'This function will be removed in a future version.',
|
||||
DeprecationWarning
|
||||
)
|
||||
|
||||
if radius is None:
|
||||
radius = 5.0
|
||||
if percent is None:
|
||||
|
@ -439,7 +489,18 @@ def unsharp_mask(im, radius=None, percent=None, threshold=None):
|
|||
|
||||
return im.im.unsharp_mask(radius, percent, threshold)
|
||||
|
||||
usm = unsharp_mask
|
||||
|
||||
def usm(im, radius=None, percent=None, threshold=None):
|
||||
""" PIL_usm.usm(im, [radius, percent, threshold])"""
|
||||
|
||||
warnings.warn(
|
||||
'PIL.ImageOps.usm is deprecated. '
|
||||
'Use PIL.ImageFilter.UnsharpMask instead. '
|
||||
'This function will be removed in a future version.',
|
||||
DeprecationWarning
|
||||
)
|
||||
|
||||
return unsharp_mask(im, radius, percent, threshold)
|
||||
|
||||
|
||||
def box_blur(image, radius):
|
||||
|
@ -456,6 +517,13 @@ def box_blur(image, radius):
|
|||
in each direction, i.e. 9 pixels in total.
|
||||
:return: An image.
|
||||
"""
|
||||
warnings.warn(
|
||||
'PIL.ImageOps.box_blur is deprecated. '
|
||||
'Use PIL.ImageFilter.BoxBlur instead. '
|
||||
'This function will be removed in a future version.',
|
||||
DeprecationWarning
|
||||
)
|
||||
|
||||
image.load()
|
||||
|
||||
return image._new(image.im.box_blur(radius))
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
#
|
||||
|
||||
import array
|
||||
from PIL import ImageColor
|
||||
from . import ImageColor, GimpPaletteFile, GimpGradientFile, PaletteFile
|
||||
|
||||
|
||||
class ImagePalette(object):
|
||||
|
@ -38,7 +38,7 @@ class ImagePalette(object):
|
|||
def __init__(self, mode="RGB", palette=None, size=0):
|
||||
self.mode = mode
|
||||
self.rawmode = None # if set, palette contains raw data
|
||||
self.palette = palette or list(range(256))*len(self.mode)
|
||||
self.palette = palette or bytearray(range(256))*len(self.mode)
|
||||
self.colors = {}
|
||||
self.dirty = None
|
||||
if ((size == 0 and len(self.mode)*256 != len(self.palette)) or
|
||||
|
@ -98,7 +98,7 @@ class ImagePalette(object):
|
|||
except KeyError:
|
||||
# allocate new color slot
|
||||
if isinstance(self.palette, bytes):
|
||||
self.palette = [int(x) for x in self.palette]
|
||||
self.palette = bytearray(self.palette)
|
||||
index = len(self.colors)
|
||||
if index >= 256:
|
||||
raise ValueError("cannot allocate more than 256 colors")
|
||||
|
@ -194,44 +194,23 @@ def load(filename):
|
|||
|
||||
# FIXME: supports GIMP gradients only
|
||||
|
||||
fp = open(filename, "rb")
|
||||
with open(filename, "rb") as fp:
|
||||
|
||||
lut = None
|
||||
|
||||
if not lut:
|
||||
for paletteHandler in [
|
||||
GimpPaletteFile.GimpPaletteFile,
|
||||
GimpGradientFile.GimpGradientFile,
|
||||
PaletteFile.PaletteFile
|
||||
]:
|
||||
try:
|
||||
from PIL import GimpPaletteFile
|
||||
fp.seek(0)
|
||||
p = GimpPaletteFile.GimpPaletteFile(fp)
|
||||
lut = p.getpalette()
|
||||
lut = paletteHandler(fp).getpalette()
|
||||
if lut:
|
||||
break
|
||||
except (SyntaxError, ValueError):
|
||||
# import traceback
|
||||
# traceback.print_exc()
|
||||
pass
|
||||
|
||||
if not lut:
|
||||
try:
|
||||
from PIL import GimpGradientFile
|
||||
fp.seek(0)
|
||||
p = GimpGradientFile.GimpGradientFile(fp)
|
||||
lut = p.getpalette()
|
||||
except (SyntaxError, ValueError):
|
||||
# import traceback
|
||||
# traceback.print_exc()
|
||||
pass
|
||||
|
||||
if not lut:
|
||||
try:
|
||||
from PIL import PaletteFile
|
||||
fp.seek(0)
|
||||
p = PaletteFile.PaletteFile(fp)
|
||||
lut = p.getpalette()
|
||||
except (SyntaxError, ValueError):
|
||||
# import traceback
|
||||
# traceback.print_exc()
|
||||
pass
|
||||
|
||||
if not lut:
|
||||
else:
|
||||
raise IOError("cannot load palette")
|
||||
|
||||
return lut # data, rawmode
|
||||
|
|
|
@ -14,53 +14,7 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from PIL import Image
|
||||
from . import Image
|
||||
|
||||
|
||||
# the Python class below is overridden by the C implementation.
|
||||
|
||||
|
||||
class Path(object):
|
||||
|
||||
def __init__(self, xy):
|
||||
pass
|
||||
|
||||
##
|
||||
# Compacts the path, by removing points that are close to each
|
||||
# other. This method modifies the path in place.
|
||||
|
||||
def compact(self, distance=2):
|
||||
pass
|
||||
|
||||
##
|
||||
# Gets the bounding box.
|
||||
|
||||
def getbbox(self):
|
||||
pass
|
||||
|
||||
##
|
||||
# Maps the path through a function.
|
||||
|
||||
def map(self, function):
|
||||
pass
|
||||
|
||||
##
|
||||
# Converts the path to Python list.
|
||||
#
|
||||
# @param flat By default, this function returns a list of 2-tuples
|
||||
# [(x, y), ...]. If this argument is true, it returns a flat
|
||||
# list [x, y, ...] instead.
|
||||
# @return A list of coordinates.
|
||||
|
||||
def tolist(self, flat=0):
|
||||
pass
|
||||
|
||||
##
|
||||
# Transforms the path.
|
||||
|
||||
def transform(self, matrix):
|
||||
pass
|
||||
|
||||
|
||||
# override with C implementation
|
||||
Path = Image.core.path
|
||||
|
|
|
@ -16,28 +16,36 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from PIL import Image
|
||||
from PIL._util import isPath
|
||||
from . import Image
|
||||
from ._util import isPath, py3
|
||||
from io import BytesIO
|
||||
import sys
|
||||
|
||||
qt_is_installed = True
|
||||
qt_version = None
|
||||
qt_versions = [
|
||||
['5', 'PyQt5'],
|
||||
['4', 'PyQt4'],
|
||||
['side', 'PySide']
|
||||
]
|
||||
# If a version has already been imported, attempt it first
|
||||
qt_versions.sort(key=lambda qt_version: qt_version[1] in sys.modules, reverse=True)
|
||||
for qt_version, qt_module in qt_versions:
|
||||
try:
|
||||
if qt_module == 'PyQt5':
|
||||
from PyQt5.QtGui import QImage, qRgba, QPixmap
|
||||
from PyQt5.QtCore import QBuffer, QIODevice
|
||||
qt_version = '5'
|
||||
except ImportError:
|
||||
try:
|
||||
elif qt_module == 'PyQt4':
|
||||
from PyQt4.QtGui import QImage, qRgba, QPixmap
|
||||
from PyQt4.QtCore import QBuffer, QIODevice
|
||||
qt_version = '4'
|
||||
except ImportError:
|
||||
try:
|
||||
elif qt_module == 'PySide':
|
||||
from PySide.QtGui import QImage, qRgba, QPixmap
|
||||
from PySide.QtCore import QBuffer, QIODevice
|
||||
qt_version = 'side'
|
||||
except ImportError:
|
||||
except (ImportError, RuntimeError):
|
||||
continue
|
||||
qt_is_installed = True
|
||||
break
|
||||
else:
|
||||
qt_is_installed = False
|
||||
qt_version = None
|
||||
|
||||
|
||||
def rgb(r, g, b, a=255):
|
||||
|
@ -47,10 +55,11 @@ def rgb(r, g, b, a=255):
|
|||
return (qRgba(r, g, b, a) & 0xffffffff)
|
||||
|
||||
|
||||
# :param im A PIL Image object, or a file name
|
||||
# (given either as Python string or a PyQt string object)
|
||||
|
||||
def fromqimage(im):
|
||||
"""
|
||||
:param im: A PIL Image object, or a file name
|
||||
(given either as Python string or a PyQt string object)
|
||||
"""
|
||||
buffer = QBuffer()
|
||||
buffer.open(QIODevice.ReadWrite)
|
||||
# preserve alha channel with png
|
||||
|
@ -122,10 +131,10 @@ def _toqclass_helper(im):
|
|||
# handle filename, if given instead of image name
|
||||
if hasattr(im, "toUtf8"):
|
||||
# FIXME - is this really the best way to do this?
|
||||
if str is bytes:
|
||||
im = unicode(im.toUtf8(), "utf-8")
|
||||
else:
|
||||
if py3:
|
||||
im = str(im.toUtf8(), "utf-8")
|
||||
else:
|
||||
im = unicode(im.toUtf8(), "utf-8")
|
||||
if isPath(im):
|
||||
im = Image.open(im)
|
||||
|
||||
|
@ -156,26 +165,31 @@ def _toqclass_helper(im):
|
|||
else:
|
||||
raise ValueError("unsupported image mode %r" % im.mode)
|
||||
|
||||
# must keep a reference, or Qt will crash!
|
||||
__data = data or align8to32(im.tobytes(), im.size[0], im.mode)
|
||||
return {
|
||||
'data': __data, 'im': im, 'format': format, 'colortable': colortable
|
||||
}
|
||||
|
||||
##
|
||||
# An PIL image wrapper for Qt. This is a subclass of PyQt's QImage
|
||||
# class.
|
||||
#
|
||||
# @param im A PIL Image object, or a file name (given either as Python
|
||||
# string or a PyQt string object).
|
||||
|
||||
if qt_is_installed:
|
||||
class ImageQt(QImage):
|
||||
|
||||
def __init__(self, im):
|
||||
"""
|
||||
An PIL image wrapper for Qt. This is a subclass of PyQt's QImage
|
||||
class.
|
||||
|
||||
:param im: A PIL Image object, or a file name (given either as Python
|
||||
string or a PyQt string object).
|
||||
"""
|
||||
im_data = _toqclass_helper(im)
|
||||
# must keep a reference, or Qt will crash!
|
||||
# All QImage constructors that take data operate on an existing
|
||||
# buffer, so this buffer has to hang on for the life of the image.
|
||||
# Fixes https://github.com/python-pillow/Pillow/issues/1370
|
||||
self.__data = im_data['data']
|
||||
QImage.__init__(self,
|
||||
im_data['data'], im_data['im'].size[0],
|
||||
self.__data, im_data['im'].size[0],
|
||||
im_data['im'].size[1], im_data['format'])
|
||||
if im_data['colortable']:
|
||||
self.setColorTable(im_data['colortable'])
|
||||
|
|
|
@ -32,11 +32,25 @@ class Iterator(object):
|
|||
if not hasattr(im, "seek"):
|
||||
raise AttributeError("im must have seek method")
|
||||
self.im = im
|
||||
self.position = 0
|
||||
|
||||
def __getitem__(self, ix):
|
||||
try:
|
||||
if ix:
|
||||
self.im.seek(ix)
|
||||
return self.im
|
||||
except EOFError:
|
||||
raise IndexError # end of sequence
|
||||
|
||||
def __iter__(self):
|
||||
return self
|
||||
|
||||
def __next__(self):
|
||||
try:
|
||||
self.im.seek(self.position)
|
||||
self.position += 1
|
||||
return self.im
|
||||
except EOFError:
|
||||
raise StopIteration
|
||||
|
||||
def next(self):
|
||||
return self.__next__()
|
||||
|
|
|
@ -18,7 +18,7 @@ from PIL import Image
|
|||
import os
|
||||
import sys
|
||||
|
||||
if sys.version_info >= (3, 3):
|
||||
if sys.version_info.major >= 3:
|
||||
from shlex import quote
|
||||
else:
|
||||
from pipes import quote
|
||||
|
@ -38,25 +38,23 @@ def register(viewer, order=1):
|
|||
_viewers.insert(0, viewer)
|
||||
|
||||
|
||||
##
|
||||
# Displays a given image.
|
||||
#
|
||||
# @param image An image object.
|
||||
# @param title Optional title. Not all viewers can display the title.
|
||||
# @param **options Additional viewer options.
|
||||
# @return True if a suitable viewer was found, false otherwise.
|
||||
|
||||
def show(image, title=None, **options):
|
||||
r"""
|
||||
Display a given image.
|
||||
|
||||
:param image: An image object.
|
||||
:param title: Optional title. Not all viewers can display the title.
|
||||
:param \**options: Additional viewer options.
|
||||
:returns: True if a suitable viewer was found, false otherwise.
|
||||
"""
|
||||
for viewer in _viewers:
|
||||
if viewer.show(image, title=title, **options):
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
##
|
||||
# Base class for viewers.
|
||||
|
||||
class Viewer(object):
|
||||
"""Base class for viewers."""
|
||||
|
||||
# main api
|
||||
|
||||
|
@ -71,7 +69,7 @@ class Viewer(object):
|
|||
# FIXME: auto-contrast if max() > 255?
|
||||
else:
|
||||
base = Image.getmodebase(image.mode)
|
||||
if base != image.mode and image.mode != "1":
|
||||
if base != image.mode and image.mode != "1" and image.mode != "RGBA":
|
||||
image = image.convert(base)
|
||||
|
||||
return self.show_image(image, **options)
|
||||
|
@ -79,29 +77,31 @@ class Viewer(object):
|
|||
# hook methods
|
||||
|
||||
format = None
|
||||
options = {}
|
||||
|
||||
def get_format(self, image):
|
||||
# return format name, or None to save as PGM/PPM
|
||||
"""Return format name, or None to save as PGM/PPM"""
|
||||
return self.format
|
||||
|
||||
def get_command(self, file, **options):
|
||||
raise NotImplementedError
|
||||
|
||||
def save_image(self, image):
|
||||
# save to temporary file, and return filename
|
||||
return image._dump(format=self.get_format(image))
|
||||
"""Save to temporary file, and return filename"""
|
||||
return image._dump(format=self.get_format(image), **self.options)
|
||||
|
||||
def show_image(self, image, **options):
|
||||
# display given image
|
||||
"""Display given image"""
|
||||
return self.show_file(self.save_image(image), **options)
|
||||
|
||||
def show_file(self, file, **options):
|
||||
# display given file
|
||||
"""Display given file"""
|
||||
os.system(self.get_command(file, **options))
|
||||
return 1
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
|
||||
if sys.platform == "win32":
|
||||
|
||||
class WindowsViewer(Viewer):
|
||||
|
@ -117,7 +117,8 @@ if sys.platform == "win32":
|
|||
elif sys.platform == "darwin":
|
||||
|
||||
class MacViewer(Viewer):
|
||||
format = "BMP"
|
||||
format = "PNG"
|
||||
options = {'compress_level': 1}
|
||||
|
||||
def get_command(self, file, **options):
|
||||
# on darwin open returns immediately resulting in the temp
|
||||
|
@ -139,12 +140,14 @@ else:
|
|||
return None
|
||||
for dirname in path.split(os.pathsep):
|
||||
filename = os.path.join(dirname, executable)
|
||||
if os.path.isfile(filename):
|
||||
# FIXME: make sure it's executable
|
||||
if os.path.isfile(filename) and os.access(filename, os.X_OK):
|
||||
return filename
|
||||
return None
|
||||
|
||||
class UnixViewer(Viewer):
|
||||
format = "PNG"
|
||||
options = {'compress_level': 1}
|
||||
|
||||
def show_file(self, file, **options):
|
||||
command, executable = self.get_command_ex(file, **options)
|
||||
command = "(%s %s; rm -f %s)&" % (command, quote(file),
|
||||
|
@ -162,6 +165,14 @@ else:
|
|||
if which("display"):
|
||||
register(DisplayViewer)
|
||||
|
||||
class EogViewer(UnixViewer):
|
||||
def get_command_ex(self, file, **options):
|
||||
command = executable = "eog"
|
||||
return command, executable
|
||||
|
||||
if which("eog"):
|
||||
register(EogViewer)
|
||||
|
||||
class XVViewer(UnixViewer):
|
||||
def get_command_ex(self, file, title=None, **options):
|
||||
# note: xv is pretty outdated. most modern systems have
|
||||
|
@ -175,5 +186,9 @@ else:
|
|||
register(XVViewer)
|
||||
|
||||
if __name__ == "__main__":
|
||||
# usage: python ImageShow.py imagefile [title]
|
||||
|
||||
if len(sys.argv) < 2:
|
||||
print("Syntax: python ImageShow.py imagefile [title]")
|
||||
sys.exit()
|
||||
|
||||
print(show(Image.open(sys.argv[1]), *sys.argv[2:]))
|
||||
|
|
|
@ -144,4 +144,5 @@ class Stat(object):
|
|||
v.append(math.sqrt(self.var[i]))
|
||||
return v
|
||||
|
||||
|
||||
Global = Stat # compatibility
|
||||
|
|
|
@ -25,14 +25,22 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
try:
|
||||
import tkinter
|
||||
except ImportError:
|
||||
import Tkinter
|
||||
tkinter = Tkinter
|
||||
del Tkinter
|
||||
import sys
|
||||
|
||||
from PIL import Image
|
||||
if sys.version_info.major > 2:
|
||||
import tkinter
|
||||
else:
|
||||
import Tkinter as tkinter
|
||||
|
||||
# required for pypy, which always has cffi installed
|
||||
try:
|
||||
from cffi import FFI
|
||||
ffi = FFI()
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
from . import Image
|
||||
from io import BytesIO
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
|
@ -53,6 +61,16 @@ def _pilbitmap_check():
|
|||
return _pilbitmap_ok
|
||||
|
||||
|
||||
def _get_image_from_kw(kw):
|
||||
source = None
|
||||
if "file" in kw:
|
||||
source = kw.pop("file")
|
||||
elif "data" in kw:
|
||||
source = BytesIO(kw.pop("data"))
|
||||
if source:
|
||||
return Image.open(source)
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# PhotoImage
|
||||
|
||||
|
@ -80,13 +98,7 @@ class PhotoImage(object):
|
|||
|
||||
# Tk compatibility: file or data
|
||||
if image is None:
|
||||
if "file" in kw:
|
||||
image = Image.open(kw["file"])
|
||||
del kw["file"]
|
||||
elif "data" in kw:
|
||||
from io import BytesIO
|
||||
image = Image.open(BytesIO(kw["data"]))
|
||||
del kw["data"]
|
||||
image = _get_image_from_kw(kw)
|
||||
|
||||
if hasattr(image, "mode") and hasattr(image, "size"):
|
||||
# got an image instead of a mode
|
||||
|
@ -157,8 +169,8 @@ class PhotoImage(object):
|
|||
mode does not match, the image is converted to the mode of
|
||||
the bitmap image.
|
||||
:param box: A 4-tuple defining the left, upper, right, and lower pixel
|
||||
coordinate. If None is given instead of a tuple, all of
|
||||
the image is assumed.
|
||||
coordinate. See :ref:`coordinate-system`. If None is given
|
||||
instead of a tuple, all of the image is assumed.
|
||||
"""
|
||||
|
||||
# convert to blittable
|
||||
|
@ -177,8 +189,14 @@ class PhotoImage(object):
|
|||
except tkinter.TclError:
|
||||
# activate Tkinter hook
|
||||
try:
|
||||
from PIL import _imagingtk
|
||||
from . import _imagingtk
|
||||
try:
|
||||
if hasattr(tk, 'interp'):
|
||||
# Pypy is using a ffi cdata element
|
||||
# (Pdb) self.tk.interp
|
||||
# <cdata 'Tcl_Interp *' 0x3061b50>
|
||||
_imagingtk.tkinit(int(ffi.cast("uintptr_t", tk.interp)), 1)
|
||||
else:
|
||||
_imagingtk.tkinit(tk.interpaddr(), 1)
|
||||
except AttributeError:
|
||||
_imagingtk.tkinit(id(tk), 0)
|
||||
|
@ -192,7 +210,6 @@ class PhotoImage(object):
|
|||
|
||||
class BitmapImage(object):
|
||||
"""
|
||||
|
||||
A Tkinter-compatible bitmap image. This can be used everywhere Tkinter
|
||||
expects an image object.
|
||||
|
||||
|
@ -209,13 +226,7 @@ class BitmapImage(object):
|
|||
|
||||
# Tk compatibility: file or data
|
||||
if image is None:
|
||||
if "file" in kw:
|
||||
image = Image.open(kw["file"])
|
||||
del kw["file"]
|
||||
elif "data" in kw:
|
||||
from io import BytesIO
|
||||
image = Image.open(BytesIO(kw["data"]))
|
||||
del kw["data"]
|
||||
image = _get_image_from_kw(kw)
|
||||
|
||||
self.__mode = image.mode
|
||||
self.__size = image.size
|
||||
|
@ -266,14 +277,14 @@ class BitmapImage(object):
|
|||
|
||||
|
||||
def getimage(photo):
|
||||
""" This function is unimplemented """
|
||||
|
||||
"""Copies the contents of a PhotoImage to a PIL image memory."""
|
||||
photo.tk.call("PyImagingPhotoGet", photo)
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# Helper for the Image.show method.
|
||||
|
||||
def _show(image, title):
|
||||
"""Helper for the Image.show method."""
|
||||
|
||||
class UI(tkinter.Label):
|
||||
def __init__(self, master, im):
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from PIL import Image
|
||||
from . import Image
|
||||
|
||||
|
||||
class Transform(Image.ImageTransformHandler):
|
||||
|
@ -29,75 +29,70 @@ class Transform(Image.ImageTransformHandler):
|
|||
return image.transform(size, method, data, **options)
|
||||
|
||||
|
||||
##
|
||||
# Define an affine image transform.
|
||||
# <p>
|
||||
# This function takes a 6-tuple (<i>a, b, c, d, e, f</i>) which
|
||||
# contain the first two rows from an affine transform matrix. For
|
||||
# each pixel (<i>x, y</i>) in the output image, the new value is
|
||||
# taken from a position (a <i>x</i> + b <i>y</i> + c,
|
||||
# d <i>x</i> + e <i>y</i> + f) in the input image, rounded to
|
||||
# nearest pixel.
|
||||
# <p>
|
||||
# This function can be used to scale, translate, rotate, and shear the
|
||||
# original image.
|
||||
#
|
||||
# @def AffineTransform(matrix)
|
||||
# @param matrix A 6-tuple (<i>a, b, c, d, e, f</i>) containing
|
||||
# the first two rows from an affine transform matrix.
|
||||
# @see Image#Image.transform
|
||||
|
||||
|
||||
class AffineTransform(Transform):
|
||||
"""
|
||||
Define an affine image transform.
|
||||
|
||||
This function takes a 6-tuple (a, b, c, d, e, f) which contain the first
|
||||
two rows from an affine transform matrix. For each pixel (x, y) in the
|
||||
output image, the new value is taken from a position (a x + b y + c,
|
||||
d x + e y + f) in the input image, rounded to nearest pixel.
|
||||
|
||||
This function can be used to scale, translate, rotate, and shear the
|
||||
original image.
|
||||
|
||||
See :py:meth:`~PIL.Image.Image.transform`
|
||||
|
||||
:param matrix: A 6-tuple (a, b, c, d, e, f) containing the first two rows
|
||||
from an affine transform matrix.
|
||||
"""
|
||||
method = Image.AFFINE
|
||||
|
||||
|
||||
##
|
||||
# Define a transform to extract a subregion from an image.
|
||||
# <p>
|
||||
# Maps a rectangle (defined by two corners) from the image to a
|
||||
# rectangle of the given size. The resulting image will contain
|
||||
# data sampled from between the corners, such that (<i>x0, y0</i>)
|
||||
# in the input image will end up at (0,0) in the output image,
|
||||
# and (<i>x1, y1</i>) at <i>size</i>.
|
||||
# <p>
|
||||
# This method can be used to crop, stretch, shrink, or mirror an
|
||||
# arbitrary rectangle in the current image. It is slightly slower than
|
||||
# <b>crop</b>, but about as fast as a corresponding <b>resize</b>
|
||||
# operation.
|
||||
#
|
||||
# @def ExtentTransform(bbox)
|
||||
# @param bbox A 4-tuple (<i>x0, y0, x1, y1</i>) which specifies
|
||||
# two points in the input image's coordinate system.
|
||||
# @see Image#Image.transform
|
||||
|
||||
class ExtentTransform(Transform):
|
||||
"""
|
||||
Define a transform to extract a subregion from an image.
|
||||
|
||||
Maps a rectangle (defined by two corners) from the image to a rectangle of
|
||||
the given size. The resulting image will contain data sampled from between
|
||||
the corners, such that (x0, y0) in the input image will end up at (0,0) in
|
||||
the output image, and (x1, y1) at size.
|
||||
|
||||
This method can be used to crop, stretch, shrink, or mirror an arbitrary
|
||||
rectangle in the current image. It is slightly slower than crop, but about
|
||||
as fast as a corresponding resize operation.
|
||||
|
||||
See :py:meth:`~PIL.Image.Image.transform`
|
||||
|
||||
:param bbox: A 4-tuple (x0, y0, x1, y1) which specifies two points in the
|
||||
input image's coordinate system. See :ref:`coordinate-system`.
|
||||
"""
|
||||
method = Image.EXTENT
|
||||
|
||||
|
||||
##
|
||||
# Define an quad image transform.
|
||||
# <p>
|
||||
# Maps a quadrilateral (a region defined by four corners) from the
|
||||
# image to a rectangle of the given size.
|
||||
#
|
||||
# @def QuadTransform(xy)
|
||||
# @param xy An 8-tuple (<i>x0, y0, x1, y1, x2, y2, y3, y3</i>) which
|
||||
# contain the upper left, lower left, lower right, and upper right
|
||||
# corner of the source quadrilateral.
|
||||
# @see Image#Image.transform
|
||||
|
||||
class QuadTransform(Transform):
|
||||
"""
|
||||
Define a quad image transform.
|
||||
|
||||
Maps a quadrilateral (a region defined by four corners) from the image to a
|
||||
rectangle of the given size.
|
||||
|
||||
See :py:meth:`~PIL.Image.Image.transform`
|
||||
|
||||
:param xy: An 8-tuple (x0, y0, x1, y1, x2, y2, x3, y3) which contain the
|
||||
upper left, lower left, lower right, and upper right corner of the
|
||||
source quadrilateral.
|
||||
"""
|
||||
method = Image.QUAD
|
||||
|
||||
|
||||
##
|
||||
# Define an mesh image transform. A mesh transform consists of one
|
||||
# or more individual quad transforms.
|
||||
#
|
||||
# @def MeshTransform(data)
|
||||
# @param data A list of (bbox, quad) tuples.
|
||||
# @see Image#Image.transform
|
||||
|
||||
class MeshTransform(Transform):
|
||||
"""
|
||||
Define a mesh image transform. A mesh transform consists of one or more
|
||||
individual quad transforms.
|
||||
|
||||
See :py:meth:`~PIL.Image.Image.transform`
|
||||
|
||||
:param data: A list of (bbox, quad) tuples.
|
||||
"""
|
||||
method = Image.MESH
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from PIL import Image
|
||||
from . import Image
|
||||
|
||||
|
||||
class HDC(object):
|
||||
|
@ -154,8 +154,9 @@ class Dib(object):
|
|||
If the mode does not match, the image is converted to the
|
||||
mode of the bitmap image.
|
||||
:param box: A 4-tuple defining the left, upper, right, and
|
||||
lower pixel coordinate. If None is given instead of a
|
||||
tuple, all of the image is assumed.
|
||||
lower pixel coordinate. See :ref:`coordinate-system`. If
|
||||
None is given instead of a tuple, all of the image is
|
||||
assumed.
|
||||
"""
|
||||
im.load()
|
||||
if self.mode != im.mode:
|
||||
|
@ -182,19 +183,9 @@ class Dib(object):
|
|||
"""
|
||||
return self.image.tobytes()
|
||||
|
||||
def fromstring(self, *args, **kw):
|
||||
raise Exception("fromstring() has been removed. " +
|
||||
"Please use frombytes() instead.")
|
||||
|
||||
def tostring(self, *args, **kw):
|
||||
raise Exception("tostring() has been removed. " +
|
||||
"Please use tobytes() instead.")
|
||||
|
||||
|
||||
##
|
||||
# Create a Window with the given title size.
|
||||
|
||||
class Window(object):
|
||||
"""Create a Window with the given title size."""
|
||||
|
||||
def __init__(self, title="PIL", width=None, height=None):
|
||||
self.hwnd = Image.core.createwindow(
|
||||
|
@ -223,10 +214,8 @@ class Window(object):
|
|||
Image.core.eventloop()
|
||||
|
||||
|
||||
##
|
||||
# Create an image window which displays the given image.
|
||||
|
||||
class ImageWindow(Window):
|
||||
"""Create an image window which displays the given image."""
|
||||
|
||||
def __init__(self, image, title="PIL"):
|
||||
if not isinstance(image, Dib):
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
import re
|
||||
|
||||
from PIL import Image, ImageFile
|
||||
from . import Image, ImageFile
|
||||
|
||||
__version__ = "0.2"
|
||||
|
||||
|
@ -69,7 +69,7 @@ class ImtImageFile(ImageFile.ImageFile):
|
|||
s = s + self.fp.readline()
|
||||
if len(s) == 1 or len(s) > 100:
|
||||
break
|
||||
if s[0] == b"*":
|
||||
if s[0] == ord(b"*"):
|
||||
continue # comment
|
||||
|
||||
m = field.match(s)
|
||||
|
|
|
@ -17,17 +17,13 @@
|
|||
|
||||
from __future__ import print_function
|
||||
|
||||
from PIL import Image, ImageFile, _binary
|
||||
from . import Image, ImageFile
|
||||
from ._binary import i8, i16be as i16, i32be as i32, o8
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
__version__ = "0.3"
|
||||
|
||||
i8 = _binary.i8
|
||||
i16 = _binary.i16be
|
||||
i32 = _binary.i32be
|
||||
o8 = _binary.o8
|
||||
|
||||
COMPRESSION = {
|
||||
1: "raw",
|
||||
5: "jpeg"
|
||||
|
@ -99,7 +95,7 @@ class IptcImageFile(ImageFile.ImageFile):
|
|||
tagdata = self.fp.read(size)
|
||||
else:
|
||||
tagdata = None
|
||||
if tag in list(self.info.keys()):
|
||||
if tag in self.info:
|
||||
if isinstance(self.info[tag], list):
|
||||
self.info[tag].append(tagdata)
|
||||
else:
|
||||
|
@ -107,7 +103,7 @@ class IptcImageFile(ImageFile.ImageFile):
|
|||
else:
|
||||
self.info[tag] = tagdata
|
||||
|
||||
# print tag, self.info[tag]
|
||||
# print(tag, self.info[tag])
|
||||
|
||||
# mode
|
||||
layers = i8(self.info[(3, 60)][0])
|
||||
|
@ -168,14 +164,9 @@ class IptcImageFile(ImageFile.ImageFile):
|
|||
o.close()
|
||||
|
||||
try:
|
||||
try:
|
||||
# fast
|
||||
self.im = Image.core.open_ppm(outfile)
|
||||
except:
|
||||
# slightly slower
|
||||
im = Image.open(outfile)
|
||||
im.load()
|
||||
self.im = im.im
|
||||
_im = Image.open(outfile)
|
||||
_im.load()
|
||||
self.im = _im.im
|
||||
finally:
|
||||
try:
|
||||
os.unlink(outfile)
|
||||
|
@ -188,16 +179,15 @@ Image.register_open(IptcImageFile.format, IptcImageFile)
|
|||
Image.register_extension(IptcImageFile.format, ".iim")
|
||||
|
||||
|
||||
##
|
||||
# Get IPTC information from TIFF, JPEG, or IPTC file.
|
||||
#
|
||||
# @param im An image containing IPTC data.
|
||||
# @return A dictionary containing IPTC information, or None if
|
||||
# no IPTC information block was found.
|
||||
|
||||
def getiptcinfo(im):
|
||||
"""
|
||||
Get IPTC information from TIFF, JPEG, or IPTC file.
|
||||
|
||||
from PIL import TiffImagePlugin, JpegImagePlugin
|
||||
:param im: An image containing IPTC data.
|
||||
:returns: A dictionary containing IPTC information, or None if
|
||||
no IPTC information block was found.
|
||||
"""
|
||||
from . import TiffImagePlugin, JpegImagePlugin
|
||||
import io
|
||||
|
||||
data = None
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
#
|
||||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
from PIL import Image, ImageFile
|
||||
from . import Image, ImageFile
|
||||
import struct
|
||||
import os
|
||||
import io
|
||||
|
@ -29,13 +29,13 @@ def _parse_codestream(fp):
|
|||
siz = hdr + fp.read(lsiz - 2)
|
||||
lsiz, rsiz, xsiz, ysiz, xosiz, yosiz, xtsiz, ytsiz, \
|
||||
xtosiz, ytosiz, csiz \
|
||||
= struct.unpack('>HHIIIIIIIIH', siz[:38])
|
||||
= struct.unpack_from('>HHIIIIIIIIH', siz)
|
||||
ssiz = [None]*csiz
|
||||
xrsiz = [None]*csiz
|
||||
yrsiz = [None]*csiz
|
||||
for i in range(csiz):
|
||||
ssiz[i], xrsiz[i], yrsiz[i] \
|
||||
= struct.unpack('>BBB', siz[36 + 3 * i:39 + 3 * i])
|
||||
= struct.unpack_from('>BBB', siz, 36 + 3 * i)
|
||||
|
||||
size = (xsiz - xosiz, ysiz - yosiz)
|
||||
if csiz == 1:
|
||||
|
@ -84,6 +84,7 @@ def _parse_jp2_header(fp):
|
|||
size = None
|
||||
mode = None
|
||||
bpc = None
|
||||
nc = None
|
||||
|
||||
hio = io.BytesIO(header)
|
||||
while True:
|
||||
|
@ -113,9 +114,9 @@ def _parse_jp2_header(fp):
|
|||
mode = 'RGBA'
|
||||
break
|
||||
elif tbox == b'colr':
|
||||
meth, prec, approx = struct.unpack('>BBB', content[:3])
|
||||
meth, prec, approx = struct.unpack_from('>BBB', content)
|
||||
if meth == 1:
|
||||
cs = struct.unpack('>I', content[3:7])[0]
|
||||
cs = struct.unpack_from('>I', content, 3)[0]
|
||||
if cs == 16: # sRGB
|
||||
if nc == 1 and (bpc & 0x7f) > 8:
|
||||
mode = 'I;16'
|
||||
|
@ -141,6 +142,9 @@ def _parse_jp2_header(fp):
|
|||
mode = 'RGBA'
|
||||
break
|
||||
|
||||
if size is None or mode is None:
|
||||
raise SyntaxError("Malformed jp2 header")
|
||||
|
||||
return (size, mode)
|
||||
|
||||
##
|
||||
|
@ -203,7 +207,7 @@ class Jpeg2KImageFile(ImageFile.ImageFile):
|
|||
t3 = (t[3][0], self.reduce, self.layers, t[3][3], t[3][4])
|
||||
self.tile = [(t[0], (0, 0) + self.size, t[2], t3)]
|
||||
|
||||
ImageFile.ImageFile.load(self)
|
||||
return ImageFile.ImageFile.load(self)
|
||||
|
||||
|
||||
def _accept(prefix):
|
||||
|
@ -262,15 +266,11 @@ def _save(im, fp, filename):
|
|||
# ------------------------------------------------------------
|
||||
# Registry stuff
|
||||
|
||||
|
||||
Image.register_open(Jpeg2KImageFile.format, Jpeg2KImageFile, _accept)
|
||||
Image.register_save(Jpeg2KImageFile.format, _save)
|
||||
|
||||
Image.register_extension(Jpeg2KImageFile.format, '.jp2')
|
||||
Image.register_extension(Jpeg2KImageFile.format, '.j2k')
|
||||
Image.register_extension(Jpeg2KImageFile.format, '.jpc')
|
||||
Image.register_extension(Jpeg2KImageFile.format, '.jpf')
|
||||
Image.register_extension(Jpeg2KImageFile.format, '.jpx')
|
||||
Image.register_extension(Jpeg2KImageFile.format, '.j2c')
|
||||
Image.register_extensions(Jpeg2KImageFile.format, [".jp2", ".j2k", ".jpc", ".jpf", ".jpx", ".j2c"])
|
||||
|
||||
Image.register_mime(Jpeg2KImageFile.format, 'image/jp2')
|
||||
Image.register_mime(Jpeg2KImageFile.format, 'image/jpx')
|
||||
|
|
|
@ -32,19 +32,16 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import array
|
||||
import struct
|
||||
import io
|
||||
import warnings
|
||||
from struct import unpack_from
|
||||
from PIL import Image, ImageFile, TiffImagePlugin, _binary
|
||||
from PIL.JpegPresets import presets
|
||||
from PIL._util import isStringType
|
||||
|
||||
i8 = _binary.i8
|
||||
o8 = _binary.o8
|
||||
i16 = _binary.i16be
|
||||
i32 = _binary.i32be
|
||||
from . import Image, ImageFile, TiffImagePlugin
|
||||
from ._binary import i8, o8, i16be as i16
|
||||
from .JpegPresets import presets
|
||||
from ._util import isStringType
|
||||
|
||||
__version__ = "0.6"
|
||||
|
||||
|
@ -86,6 +83,7 @@ def APP(self, marker):
|
|||
self.info["jfif_unit"] = jfif_unit
|
||||
self.info["jfif_density"] = jfif_density
|
||||
elif marker == 0xFFE1 and s[:5] == b"Exif\0":
|
||||
if "exif" not in self.info:
|
||||
# extract Exif information (incomplete)
|
||||
self.info["exif"] = s # FIXME: value will change
|
||||
elif marker == 0xFFE2 and s[:5] == b"FPXR\0":
|
||||
|
@ -120,6 +118,26 @@ def APP(self, marker):
|
|||
# plus constant header size
|
||||
self.info["mpoffset"] = self.fp.tell() - n + 4
|
||||
|
||||
# If DPI isn't in JPEG header, fetch from EXIF
|
||||
if "dpi" not in self.info and "exif" in self.info:
|
||||
try:
|
||||
exif = self._getexif()
|
||||
resolution_unit = exif[0x0128]
|
||||
x_resolution = exif[0x011A]
|
||||
try:
|
||||
dpi = x_resolution[0] / x_resolution[1]
|
||||
except TypeError:
|
||||
dpi = x_resolution
|
||||
if resolution_unit == 3: # cm
|
||||
# 1 dpcm = 2.54 dpi
|
||||
dpi *= 2.54
|
||||
self.info["dpi"] = dpi, dpi
|
||||
except (KeyError, SyntaxError, ZeroDivisionError):
|
||||
# SyntaxError for invalid/unreadable exif
|
||||
# KeyError for dpi not included
|
||||
# ZeroDivisionError for invalid dpi rational value
|
||||
self.info["dpi"] = 72, 72
|
||||
|
||||
|
||||
def COM(self, marker):
|
||||
#
|
||||
|
@ -195,7 +213,7 @@ def DQT(self, marker):
|
|||
raise SyntaxError("bad quantization table marker")
|
||||
v = i8(s[0])
|
||||
if v//16 == 0:
|
||||
self.quantization[v & 15] = array.array("b", s[1:65])
|
||||
self.quantization[v & 15] = array.array("B", s[1:65])
|
||||
s = s[65:]
|
||||
else:
|
||||
return # FIXME: add code to read 16-bit tables!
|
||||
|
@ -316,7 +334,7 @@ class JpegImageFile(ImageFile.ImageFile):
|
|||
|
||||
if i in MARKER:
|
||||
name, description, handler = MARKER[i]
|
||||
# print hex(i), name, description
|
||||
# print(hex(i), name, description)
|
||||
if handler is not None:
|
||||
handler(self, i)
|
||||
if i == 0xFFDA: # start of scan
|
||||
|
@ -331,14 +349,35 @@ class JpegImageFile(ImageFile.ImageFile):
|
|||
elif i == 0 or i == 0xFFFF:
|
||||
# padded marker or junk; move on
|
||||
s = b"\xff"
|
||||
elif i == 0xFF00: # Skip extraneous data (escaped 0xFF)
|
||||
s = self.fp.read(1)
|
||||
else:
|
||||
raise SyntaxError("no marker found")
|
||||
|
||||
def load_read(self, read_bytes):
|
||||
"""
|
||||
internal: read more image data
|
||||
For premature EOF and LOAD_TRUNCATED_IMAGES adds EOI marker
|
||||
so libjpeg can finish decoding
|
||||
"""
|
||||
s = self.fp.read(read_bytes)
|
||||
|
||||
if not s and ImageFile.LOAD_TRUNCATED_IMAGES:
|
||||
# Premature EOF.
|
||||
# Pretend file is finished adding EOI marker
|
||||
return b"\xFF\xD9"
|
||||
|
||||
return s
|
||||
|
||||
def draft(self, mode, size):
|
||||
|
||||
if len(self.tile) != 1:
|
||||
return
|
||||
|
||||
# Protect from second call
|
||||
if self.decoderconfig:
|
||||
return
|
||||
|
||||
d, e, o, a = self.tile[0]
|
||||
scale = 0
|
||||
|
||||
|
@ -347,7 +386,7 @@ class JpegImageFile(ImageFile.ImageFile):
|
|||
a = mode, ""
|
||||
|
||||
if size:
|
||||
scale = max(self.size[0] // size[0], self.size[1] // size[1])
|
||||
scale = min(self.size[0] // size[0], self.size[1] // size[1])
|
||||
for s in [8, 4, 2, 1]:
|
||||
if scale >= s:
|
||||
break
|
||||
|
@ -375,7 +414,9 @@ class JpegImageFile(ImageFile.ImageFile):
|
|||
raise ValueError("Invalid Filename")
|
||||
|
||||
try:
|
||||
self.im = Image.core.open_ppm(path)
|
||||
_im = Image.open(path)
|
||||
_im.load()
|
||||
self.im = _im.im
|
||||
finally:
|
||||
try:
|
||||
os.unlink(path)
|
||||
|
@ -399,12 +440,13 @@ def _fixup_dict(src_dict):
|
|||
# returns a dict with any single item tuples/lists as individual values
|
||||
def _fixup(value):
|
||||
try:
|
||||
if len(value) == 1 and type(value) != type({}):
|
||||
if len(value) == 1 and not isinstance(value, dict):
|
||||
return value[0]
|
||||
except: pass
|
||||
except:
|
||||
pass
|
||||
return value
|
||||
|
||||
return dict([(k, _fixup(v)) for k, v in src_dict.items()])
|
||||
return {k: _fixup(v) for k, v in src_dict.items()}
|
||||
|
||||
|
||||
def _getexif(self):
|
||||
|
@ -483,8 +525,8 @@ def _getmp(self):
|
|||
try:
|
||||
rawmpentries = mp[0xB002]
|
||||
for entrynum in range(0, quant):
|
||||
unpackedentry = unpack_from(
|
||||
'{0}LLLHH'.format(endianness), rawmpentries, entrynum * 16)
|
||||
unpackedentry = struct.unpack_from(
|
||||
'{}LLLHH'.format(endianness), rawmpentries, entrynum * 16)
|
||||
labels = ('Attribute', 'Size', 'DataOffset', 'EntryNo1',
|
||||
'EntryNo2')
|
||||
mpentry = dict(zip(labels, unpackedentry))
|
||||
|
@ -532,7 +574,6 @@ RAWMODE = {
|
|||
"1": "L",
|
||||
"L": "L",
|
||||
"RGB": "RGB",
|
||||
"RGBA": "RGB",
|
||||
"RGBX": "RGB",
|
||||
"CMYK": "CMYK;I", # assume adobe conventions
|
||||
"YCbCr": "YCbCr",
|
||||
|
@ -583,7 +624,7 @@ def _save(im, fp, filename):
|
|||
|
||||
info = im.encoderinfo
|
||||
|
||||
dpi = info.get("dpi", (0, 0))
|
||||
dpi = [int(round(x)) for x in info.get("dpi", (0, 0))]
|
||||
|
||||
quality = info.get("quality", 0)
|
||||
subsampling = info.get("subsampling", -1)
|
||||
|
@ -610,7 +651,11 @@ def _save(im, fp, filename):
|
|||
subsampling = 0
|
||||
elif subsampling == "4:2:2":
|
||||
subsampling = 1
|
||||
elif subsampling == "4:2:0":
|
||||
subsampling = 2
|
||||
elif subsampling == "4:1:1":
|
||||
# For compatibility. Before Pillow 4.3, 4:1:1 actually meant 4:2:0.
|
||||
# Set 4:2:0 if someone is still using that value.
|
||||
subsampling = 2
|
||||
elif subsampling == "keep":
|
||||
if im.format != "JPEG":
|
||||
|
@ -639,8 +684,8 @@ def _save(im, fp, filename):
|
|||
for idx, table in enumerate(qtables):
|
||||
try:
|
||||
if len(table) != 64:
|
||||
raise
|
||||
table = array.array('b', table)
|
||||
raise TypeError
|
||||
table = array.array('B', table)
|
||||
except TypeError:
|
||||
raise ValueError("Invalid quantization table")
|
||||
else:
|
||||
|
@ -672,15 +717,20 @@ def _save(im, fp, filename):
|
|||
o8(len(markers)) + marker)
|
||||
i += 1
|
||||
|
||||
# get keyword arguments
|
||||
im.encoderconfig = (
|
||||
quality,
|
||||
# "progressive" is the official name, but older documentation
|
||||
# says "progression"
|
||||
# FIXME: issue a warning if the wrong form is used (post-1.1.7)
|
||||
"progressive" in info or "progression" in info,
|
||||
progressive = (info.get("progressive", False) or
|
||||
info.get("progression", False))
|
||||
|
||||
optimize = info.get("optimize", False)
|
||||
|
||||
# get keyword arguments
|
||||
im.encoderconfig = (
|
||||
quality,
|
||||
progressive,
|
||||
info.get("smooth", 0),
|
||||
"optimize" in info,
|
||||
optimize,
|
||||
info.get("streamtype", 0),
|
||||
dpi[0], dpi[1],
|
||||
subsampling,
|
||||
|
@ -690,20 +740,24 @@ def _save(im, fp, filename):
|
|||
)
|
||||
|
||||
# if we optimize, libjpeg needs a buffer big enough to hold the whole image
|
||||
# in a shot. Guessing on the size, at im.size bytes. (raw pizel size is
|
||||
# in a shot. Guessing on the size, at im.size bytes. (raw pixel size is
|
||||
# channels*size, this is a value that's been used in a django patch.
|
||||
# https://github.com/matthewwithanm/django-imagekit/issues/50
|
||||
bufsize = 0
|
||||
if "optimize" in info or "progressive" in info or "progression" in info:
|
||||
if optimize or progressive:
|
||||
# CMYK can be bigger
|
||||
if im.mode == 'CMYK':
|
||||
bufsize = 4 * im.size[0] * im.size[1]
|
||||
# keep sets quality to 0, but the actual value may be high.
|
||||
if quality >= 95 or quality == 0:
|
||||
elif quality >= 95 or quality == 0:
|
||||
bufsize = 2 * im.size[0] * im.size[1]
|
||||
else:
|
||||
bufsize = im.size[0] * im.size[1]
|
||||
|
||||
# The exif info needs to be written as one block, + APP1, + one spare byte.
|
||||
# Ensure that our buffer is big enough
|
||||
bufsize = max(ImageFile.MAXBLOCK, bufsize, len(info.get("exif", b"")) + 5)
|
||||
# Ensure that our buffer is big enough. Same with the icc_profile block.
|
||||
bufsize = max(ImageFile.MAXBLOCK, bufsize, len(info.get("exif", b"")) + 5,
|
||||
len(extra) + 1)
|
||||
|
||||
ImageFile._save(im, fp, [("jpeg", (0, 0)+im.size, 0, rawmode)], bufsize)
|
||||
|
||||
|
@ -745,9 +799,6 @@ def jpeg_factory(fp=None, filename=None):
|
|||
Image.register_open(JpegImageFile.format, jpeg_factory, _accept)
|
||||
Image.register_save(JpegImageFile.format, _save)
|
||||
|
||||
Image.register_extension(JpegImageFile.format, ".jfif")
|
||||
Image.register_extension(JpegImageFile.format, ".jpe")
|
||||
Image.register_extension(JpegImageFile.format, ".jpg")
|
||||
Image.register_extension(JpegImageFile.format, ".jpeg")
|
||||
Image.register_extensions(JpegImageFile.format, [".jfif", ".jpe", ".jpg", ".jpeg"])
|
||||
|
||||
Image.register_mime(JpegImageFile.format, "image/jpeg")
|
||||
|
|
|
@ -30,7 +30,7 @@ for chroma information than for luma information.
|
|||
(ref.: https://en.wikipedia.org/wiki/Chroma_subsampling)
|
||||
|
||||
Possible subsampling values are 0, 1 and 2 that correspond to 4:4:4, 4:2:2 and
|
||||
4:1:1 (or 4:2:0?).
|
||||
4:2:0.
|
||||
|
||||
You can get the subsampling of a JPEG with the
|
||||
`JpegImagePlugin.get_subsampling(im)` function.
|
||||
|
@ -62,12 +62,12 @@ The tables format between im.quantization and quantization in presets differ in
|
|||
You can convert the dict format to the preset format with the
|
||||
`JpegImagePlugin.convert_dict_qtables(dict_qtables)` function.
|
||||
|
||||
Libjpeg ref.: http://www.jpegcameras.com/libjpeg/libjpeg-3.html
|
||||
Libjpeg ref.: https://web.archive.org/web/20120328125543/http://www.jpegcameras.com/libjpeg/libjpeg-3.html
|
||||
|
||||
"""
|
||||
|
||||
presets = {
|
||||
'web_low': {'subsampling': 2, # "4:1:1"
|
||||
'web_low': {'subsampling': 2, # "4:2:0"
|
||||
'quantization': [
|
||||
[20, 16, 25, 39, 50, 46, 62, 68,
|
||||
16, 18, 23, 38, 38, 53, 65, 68,
|
||||
|
@ -86,7 +86,7 @@ presets = {
|
|||
68, 68, 68, 68, 68, 68, 68, 68,
|
||||
68, 68, 68, 68, 68, 68, 68, 68]
|
||||
]},
|
||||
'web_medium': {'subsampling': 2, # "4:1:1"
|
||||
'web_medium': {'subsampling': 2, # "4:2:0"
|
||||
'quantization': [
|
||||
[16, 11, 11, 16, 23, 27, 31, 30,
|
||||
11, 12, 12, 15, 20, 23, 23, 30,
|
||||
|
@ -162,7 +162,7 @@ presets = {
|
|||
3, 3, 3, 3, 3, 3, 3, 3,
|
||||
3, 3, 3, 3, 3, 3, 3, 3]
|
||||
]},
|
||||
'low': {'subsampling': 2, # "4:1:1"
|
||||
'low': {'subsampling': 2, # "4:2:0"
|
||||
'quantization': [
|
||||
[18, 14, 14, 21, 30, 35, 34, 17,
|
||||
14, 16, 16, 19, 26, 23, 12, 12,
|
||||
|
@ -181,7 +181,7 @@ presets = {
|
|||
17, 12, 12, 12, 12, 12, 12, 12,
|
||||
17, 12, 12, 12, 12, 12, 12, 12]
|
||||
]},
|
||||
'medium': {'subsampling': 2, # "4:1:1"
|
||||
'medium': {'subsampling': 2, # "4:2:0"
|
||||
'quantization': [
|
||||
[12, 8, 8, 12, 17, 21, 24, 17,
|
||||
8, 9, 9, 11, 15, 19, 12, 12,
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
#
|
||||
|
||||
import struct
|
||||
from PIL import Image, ImageFile
|
||||
from . import Image, ImageFile
|
||||
|
||||
__version__ = "0.2"
|
||||
|
||||
|
@ -66,6 +66,7 @@ class McIdasImageFile(ImageFile.ImageFile):
|
|||
|
||||
self.tile = [("raw", (0, 0) + self.size, offset, (rawmode, stride, 1))]
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# registry
|
||||
|
||||
|
|
|
@ -17,8 +17,9 @@
|
|||
#
|
||||
|
||||
|
||||
from PIL import Image, TiffImagePlugin
|
||||
from PIL.OleFileIO import MAGIC, OleFileIO
|
||||
from . import Image, TiffImagePlugin
|
||||
|
||||
import olefile
|
||||
|
||||
__version__ = "0.1"
|
||||
|
||||
|
@ -28,7 +29,7 @@ __version__ = "0.1"
|
|||
|
||||
|
||||
def _accept(prefix):
|
||||
return prefix[:8] == MAGIC
|
||||
return prefix[:8] == olefile.MAGIC
|
||||
|
||||
|
||||
##
|
||||
|
@ -38,6 +39,7 @@ class MicImageFile(TiffImagePlugin.TiffImageFile):
|
|||
|
||||
format = "MIC"
|
||||
format_description = "Microsoft Image Composer"
|
||||
_close_exclusive_fp_after_loading = False
|
||||
|
||||
def _open(self):
|
||||
|
||||
|
@ -45,7 +47,7 @@ class MicImageFile(TiffImagePlugin.TiffImageFile):
|
|||
# to be a Microsoft Image Composer file
|
||||
|
||||
try:
|
||||
self.ole = OleFileIO(self.fp)
|
||||
self.ole = olefile.OleFileIO(self.fp)
|
||||
except IOError:
|
||||
raise SyntaxError("not an MIC file; invalid OLE file")
|
||||
|
||||
|
@ -63,7 +65,7 @@ class MicImageFile(TiffImagePlugin.TiffImageFile):
|
|||
raise SyntaxError("not an MIC file; no image entries")
|
||||
|
||||
self.__fp = self.fp
|
||||
self.frame = 0
|
||||
self.frame = None
|
||||
|
||||
if len(self.images) > 1:
|
||||
self.category = Image.CONTAINER
|
||||
|
@ -79,7 +81,8 @@ class MicImageFile(TiffImagePlugin.TiffImageFile):
|
|||
return len(self.images) > 1
|
||||
|
||||
def seek(self, frame):
|
||||
|
||||
if not self._seek_check(frame):
|
||||
return
|
||||
try:
|
||||
filename = self.images[frame]
|
||||
except IndexError:
|
||||
|
@ -95,6 +98,7 @@ class MicImageFile(TiffImagePlugin.TiffImageFile):
|
|||
|
||||
return self.frame
|
||||
|
||||
|
||||
#
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
|
|
|
@ -14,8 +14,8 @@
|
|||
#
|
||||
|
||||
|
||||
from PIL import Image, ImageFile
|
||||
from PIL._binary import i8
|
||||
from . import Image, ImageFile
|
||||
from ._binary import i8
|
||||
|
||||
__version__ = "0.1"
|
||||
|
||||
|
@ -80,7 +80,6 @@ class MpegImageFile(ImageFile.ImageFile):
|
|||
|
||||
Image.register_open(MpegImageFile.format, MpegImageFile)
|
||||
|
||||
Image.register_extension(MpegImageFile.format, ".mpg")
|
||||
Image.register_extension(MpegImageFile.format, ".mpeg")
|
||||
Image.register_extensions(MpegImageFile.format, [".mpg", ".mpeg"])
|
||||
|
||||
Image.register_mime(MpegImageFile.format, "video/mpeg")
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from PIL import Image, JpegImagePlugin
|
||||
from . import Image, JpegImagePlugin
|
||||
|
||||
__version__ = "0.1"
|
||||
|
||||
|
@ -39,6 +39,7 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile):
|
|||
|
||||
format = "MPO"
|
||||
format_description = "MPO (CIPA DC-007)"
|
||||
_close_exclusive_fp_after_loading = False
|
||||
|
||||
def _open(self):
|
||||
self.fp.seek(0) # prep the fp in order to pass the JPEG test
|
||||
|
@ -71,9 +72,8 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile):
|
|||
return self.__framecount > 1
|
||||
|
||||
def seek(self, frame):
|
||||
if frame < 0 or frame >= self.__framecount:
|
||||
raise EOFError("no more images in MPO file")
|
||||
else:
|
||||
if not self._seek_check(frame):
|
||||
return
|
||||
self.fp = self.__fp
|
||||
self.offset = self.__mpoffsets[frame]
|
||||
self.tile = [
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
#
|
||||
# The Python Imaging Library.
|
||||
# $Id$
|
||||
#
|
||||
# MSP file handling
|
||||
#
|
||||
|
@ -9,15 +8,25 @@
|
|||
# History:
|
||||
# 95-09-05 fl Created
|
||||
# 97-01-03 fl Read/write MSP images
|
||||
# 17-02-21 es Fixed RLE interpretation
|
||||
#
|
||||
# Copyright (c) Secret Labs AB 1997.
|
||||
# Copyright (c) Fredrik Lundh 1995-97.
|
||||
# Copyright (c) Eric Soroos 2017.
|
||||
#
|
||||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
# More info on this format: https://archive.org/details/gg243631
|
||||
# Page 313:
|
||||
# Figure 205. Windows Paint Version 1: "DanM" Format
|
||||
# Figure 206. Windows Paint Version 2: "LinS" Format. Used in Windows V2.03
|
||||
#
|
||||
# See also: http://www.fileformat.info/format/mspaint/egff.htm
|
||||
|
||||
|
||||
from PIL import Image, ImageFile, _binary
|
||||
from . import Image, ImageFile
|
||||
from ._binary import i16le as i16, o16le as o16, i8
|
||||
import struct
|
||||
import io
|
||||
|
||||
__version__ = "0.1"
|
||||
|
||||
|
@ -25,8 +34,6 @@ __version__ = "0.1"
|
|||
#
|
||||
# read MSP files
|
||||
|
||||
i16 = _binary.i16le
|
||||
|
||||
|
||||
def _accept(prefix):
|
||||
return prefix[:4] in [b"DanM", b"LinS"]
|
||||
|
@ -61,13 +68,93 @@ class MspImageFile(ImageFile.ImageFile):
|
|||
if s[:4] == b"DanM":
|
||||
self.tile = [("raw", (0, 0)+self.size, 32, ("1", 0, 1))]
|
||||
else:
|
||||
self.tile = [("msp", (0, 0)+self.size, 32+2*self.size[1], None)]
|
||||
self.tile = [("MSP", (0, 0)+self.size, 32, None)]
|
||||
|
||||
|
||||
class MspDecoder(ImageFile.PyDecoder):
|
||||
# The algo for the MSP decoder is from
|
||||
# http://www.fileformat.info/format/mspaint/egff.htm
|
||||
# cc-by-attribution -- That page references is taken from the
|
||||
# Encyclopedia of Graphics File Formats and is licensed by
|
||||
# O'Reilly under the Creative Common/Attribution license
|
||||
#
|
||||
# For RLE encoded files, the 32byte header is followed by a scan
|
||||
# line map, encoded as one 16bit word of encoded byte length per
|
||||
# line.
|
||||
#
|
||||
# NOTE: the encoded length of the line can be 0. This was not
|
||||
# handled in the previous version of this encoder, and there's no
|
||||
# mention of how to handle it in the documentation. From the few
|
||||
# examples I've seen, I've assumed that it is a fill of the
|
||||
# background color, in this case, white.
|
||||
#
|
||||
#
|
||||
# Pseudocode of the decoder:
|
||||
# Read a BYTE value as the RunType
|
||||
# If the RunType value is zero
|
||||
# Read next byte as the RunCount
|
||||
# Read the next byte as the RunValue
|
||||
# Write the RunValue byte RunCount times
|
||||
# If the RunType value is non-zero
|
||||
# Use this value as the RunCount
|
||||
# Read and write the next RunCount bytes literally
|
||||
#
|
||||
# e.g.:
|
||||
# 0x00 03 ff 05 00 01 02 03 04
|
||||
# would yield the bytes:
|
||||
# 0xff ff ff 00 01 02 03 04
|
||||
#
|
||||
# which are then interpreted as a bit packed mode '1' image
|
||||
|
||||
_pulls_fd = True
|
||||
|
||||
def decode(self, buffer):
|
||||
|
||||
img = io.BytesIO()
|
||||
blank_line = bytearray((0xff,)*((self.state.xsize+7)//8))
|
||||
try:
|
||||
self.fd.seek(32)
|
||||
rowmap = struct.unpack_from("<%dH" % (self.state.ysize),
|
||||
self.fd.read(self.state.ysize*2))
|
||||
except struct.error:
|
||||
raise IOError("Truncated MSP file in row map")
|
||||
|
||||
for x, rowlen in enumerate(rowmap):
|
||||
try:
|
||||
if rowlen == 0:
|
||||
img.write(blank_line)
|
||||
continue
|
||||
row = self.fd.read(rowlen)
|
||||
if len(row) != rowlen:
|
||||
raise IOError("Truncated MSP file, expected %d bytes on row %s",
|
||||
(rowlen, x))
|
||||
idx = 0
|
||||
while idx < rowlen:
|
||||
runtype = i8(row[idx])
|
||||
idx += 1
|
||||
if runtype == 0:
|
||||
(runcount, runval) = struct.unpack_from("Bc", row, idx)
|
||||
img.write(runval * runcount)
|
||||
idx += 2
|
||||
else:
|
||||
runcount = runtype
|
||||
img.write(row[idx:idx+runcount])
|
||||
idx += runcount
|
||||
|
||||
except struct.error:
|
||||
raise IOError("Corrupted MSP file in row %d" % x)
|
||||
|
||||
self.set_as_raw(img.getvalue(), ("1", 0, 1))
|
||||
|
||||
return 0, 0
|
||||
|
||||
|
||||
Image.register_decoder('MSP', MspDecoder)
|
||||
|
||||
|
||||
#
|
||||
# write MSP files (uncompressed only)
|
||||
|
||||
o16 = _binary.o16le
|
||||
|
||||
|
||||
def _save(im, fp, filename):
|
||||
|
||||
|
@ -95,6 +182,7 @@ def _save(im, fp, filename):
|
|||
# image body
|
||||
ImageFile._save(im, fp, [("raw", (0, 0)+im.size, 32, ("1", 0, 1))])
|
||||
|
||||
|
||||
#
|
||||
# registry
|
||||
|
||||
|
|
|
@ -1,180 +0,0 @@
|
|||
olefile (formerly OleFileIO_PL)
|
||||
===============================
|
||||
|
||||
[olefile](http://www.decalage.info/olefile) is a Python package to parse, read and write
|
||||
[Microsoft OLE2 files](http://en.wikipedia.org/wiki/Compound_File_Binary_Format)
|
||||
(also called Structured Storage, Compound File Binary Format or Compound Document File Format),
|
||||
such as Microsoft Office 97-2003 documents, vbaProject.bin in MS Office 2007+ files, Image Composer
|
||||
and FlashPix files, Outlook messages, StickyNotes, several Microscopy file formats, McAfee antivirus quarantine files,
|
||||
etc.
|
||||
|
||||
|
||||
**Quick links:** [Home page](http://www.decalage.info/olefile) -
|
||||
[Download/Install](https://bitbucket.org/decalage/olefileio_pl/wiki/Install) -
|
||||
[Documentation](https://bitbucket.org/decalage/olefileio_pl/wiki) -
|
||||
[Report Issues/Suggestions/Questions](https://bitbucket.org/decalage/olefileio_pl/issues?status=new&status=open) -
|
||||
[Contact the author](http://decalage.info/contact) -
|
||||
[Repository](https://bitbucket.org/decalage/olefileio_pl) -
|
||||
[Updates on Twitter](https://twitter.com/decalage2)
|
||||
|
||||
|
||||
News
|
||||
----
|
||||
|
||||
Follow all updates and news on Twitter: <https://twitter.com/decalage2>
|
||||
|
||||
- **2015-01-25 v0.42**: improved handling of special characters in stream/storage names on Python 2.x (using UTF-8
|
||||
instead of Latin-1), fixed bug in listdir with empty storages.
|
||||
- 2014-11-25 v0.41: OleFileIO.open and isOleFile now support OLE files stored in byte strings, fixed installer for
|
||||
python 3, added support for Jython (Niko Ehrenfeuchter)
|
||||
- 2014-10-01 v0.40: renamed OleFileIO_PL to olefile, added initial write support for streams >4K, updated doc and
|
||||
license, improved the setup script.
|
||||
- 2014-07-27 v0.31: fixed support for large files with 4K sectors, thanks to Niko Ehrenfeuchter, Martijn Berger and
|
||||
Dave Jones. Added test scripts from Pillow (by hugovk). Fixed setup for Python 3 (Martin Panter)
|
||||
- 2014-02-04 v0.30: now compatible with Python 3.x, thanks to Martin Panter who did most of the hard work.
|
||||
- 2013-07-24 v0.26: added methods to parse stream/storage timestamps, improved listdir to include storages, fixed
|
||||
parsing of direntry timestamps
|
||||
- 2013-05-27 v0.25: improved metadata extraction, properties parsing and exception handling, fixed
|
||||
[issue #12](https://bitbucket.org/decalage/olefileio_pl/issue/12/error-when-converting-timestamps-in-ole)
|
||||
- 2013-05-07 v0.24: new features to extract metadata (get\_metadata method and OleMetadata class), improved
|
||||
getproperties to convert timestamps to Python datetime
|
||||
- 2012-10-09: published [python-oletools](http://www.decalage.info/python/oletools), a package of analysis tools based
|
||||
on OleFileIO_PL
|
||||
- 2012-09-11 v0.23: added support for file-like objects, fixed [issue #8](https://bitbucket.org/decalage/olefileio_pl/issue/8/bug-with-file-object)
|
||||
- 2012-02-17 v0.22: fixed issues #7 (bug in getproperties) and #2 (added close method)
|
||||
- 2011-10-20: code hosted on bitbucket to ease contributions and bug tracking
|
||||
- 2010-01-24 v0.21: fixed support for big-endian CPUs, such as PowerPC Macs.
|
||||
- 2009-12-11 v0.20: small bugfix in OleFileIO.open when filename is not plain str.
|
||||
- 2009-12-10 v0.19: fixed support for 64 bits platforms (thanks to Ben G. and Martijn for reporting the bug)
|
||||
- see changelog in source code for more info.
|
||||
|
||||
Download/Install
|
||||
----------------
|
||||
|
||||
If you have pip or setuptools installed (pip is included in Python 2.7.9+), you may simply run **pip install olefile**
|
||||
or **easy_install olefile** for the first installation.
|
||||
|
||||
To update olefile, run **pip install -U olefile**.
|
||||
|
||||
Otherwise, see https://bitbucket.org/decalage/olefileio_pl/wiki/Install
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
- Parse, read and write any OLE file such as Microsoft Office 97-2003 legacy document formats (Word .doc, Excel .xls,
|
||||
PowerPoint .ppt, Visio .vsd, Project .mpp), Image Composer and FlashPix files, Outlook messages, StickyNotes,
|
||||
Zeiss AxioVision ZVI files, Olympus FluoView OIB files, etc
|
||||
- List all the streams and storages contained in an OLE file
|
||||
- Open streams as files
|
||||
- Parse and read property streams, containing metadata of the file
|
||||
- Portable, pure Python module, no dependency
|
||||
|
||||
olefile can be used as an independent package or with PIL/Pillow.
|
||||
|
||||
olefile is mostly meant for developers. If you are looking for tools to analyze OLE files or to extract data (especially
|
||||
for security purposes such as malware analysis and forensics), then please also check my
|
||||
[python-oletools](http://www.decalage.info/python/oletools), which are built upon olefile and provide a higher-level interface.
|
||||
|
||||
|
||||
History
|
||||
-------
|
||||
|
||||
olefile is based on the OleFileIO module from [PIL](http://www.pythonware.com/products/pil/index.htm), the excellent
|
||||
Python Imaging Library, created and maintained by Fredrik Lundh. The olefile API is still compatible with PIL, but
|
||||
since 2005 I have improved the internal implementation significantly, with new features, bugfixes and a more robust
|
||||
design. From 2005 to 2014 the project was called OleFileIO_PL, and in 2014 I changed its name to olefile to celebrate
|
||||
its 9 years and its new write features.
|
||||
|
||||
As far as I know, olefile is the most complete and robust Python implementation to read MS OLE2 files, portable on
|
||||
several operating systems. (please tell me if you know other similar Python modules)
|
||||
|
||||
Since 2014 olefile/OleFileIO_PL has been integrated into [Pillow](http://python-imaging.github.io/), the friendly fork
|
||||
of PIL. olefile will continue to be improved as a separate project, and new versions will be merged into Pillow
|
||||
regularly.
|
||||
|
||||
|
||||
Main improvements over the original version of OleFileIO in PIL:
|
||||
----------------------------------------------------------------
|
||||
|
||||
- Compatible with Python 3.x and 2.6+
|
||||
- Many bug fixes
|
||||
- Support for files larger than 6.8MB
|
||||
- Support for 64 bits platforms and big-endian CPUs
|
||||
- Robust: many checks to detect malformed files
|
||||
- Runtime option to choose if malformed files should be parsed or raise exceptions
|
||||
- Improved API
|
||||
- Metadata extraction, stream/storage timestamps (e.g. for document forensics)
|
||||
- Can open file-like objects
|
||||
- Added setup.py and install.bat to ease installation
|
||||
- More convenient slash-based syntax for stream paths
|
||||
- Write features
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
Please see the [online documentation](https://bitbucket.org/decalage/olefileio_pl/wiki) for more information,
|
||||
especially the [OLE overview](https://bitbucket.org/decalage/olefileio_pl/wiki/OLE_Overview) and the
|
||||
[API page](https://bitbucket.org/decalage/olefileio_pl/wiki/API) which describe how to use olefile in Python applications.
|
||||
A copy of the same documentation is also provided in the doc subfolder of the olefile package.
|
||||
|
||||
|
||||
## Real-life examples ##
|
||||
|
||||
A real-life example: [using OleFileIO_PL for malware analysis and forensics](http://blog.gregback.net/2011/03/using-remnux-for-forensic-puzzle-6/).
|
||||
|
||||
See also [this paper](https://computer-forensics.sans.org/community/papers/gcfa/grow-forensic-tools-taxonomy-python-libraries-helpful-forensic-analysis_6879) about python tools for forensics, which features olefile.
|
||||
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
olefile (formerly OleFileIO_PL) is copyright (c) 2005-2015 Philippe Lagadec
|
||||
([http://www.decalage.info](http://www.decalage.info))
|
||||
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
----------
|
||||
|
||||
olefile is based on source code from the OleFileIO module of the Python Imaging Library (PIL) published by Fredrik
|
||||
Lundh under the following license:
|
||||
|
||||
The Python Imaging Library (PIL) is
|
||||
|
||||
- Copyright (c) 1997-2005 by Secret Labs AB
|
||||
- Copyright (c) 1995-2005 by Fredrik Lundh
|
||||
|
||||
By obtaining, using, and/or copying this software and/or its associated documentation, you agree that you have read,
|
||||
understood, and will comply with the following terms and conditions:
|
||||
|
||||
Permission to use, copy, modify, and distribute this software and its associated documentation for any purpose and
|
||||
without fee is hereby granted, provided that the above copyright notice appears in all copies, and that both that
|
||||
copyright notice and this permission notice appear in supporting documentation, and that the name of Secret Labs AB or
|
||||
the author not be used in advertising or publicity pertaining to distribution of the software without specific, written
|
||||
prior permission.
|
||||
|
||||
SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES
|
||||
OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR
|
||||
CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
|
||||
CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
|
||||
SOFTWARE.
|
File diff suppressed because it is too large
Load Diff
|
@ -15,7 +15,8 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from PIL import EpsImagePlugin
|
||||
from . import EpsImagePlugin
|
||||
from ._util import py3
|
||||
import sys
|
||||
|
||||
##
|
||||
|
@ -24,7 +25,7 @@ import sys
|
|||
|
||||
class PSDraw(object):
|
||||
"""
|
||||
Sets up printing to the given file. If **file** is omitted,
|
||||
Sets up printing to the given file. If **fp** is omitted,
|
||||
:py:attr:`sys.stdout` is assumed.
|
||||
"""
|
||||
|
||||
|
@ -34,7 +35,7 @@ class PSDraw(object):
|
|||
self.fp = fp
|
||||
|
||||
def _fp_write(self, to_write):
|
||||
if bytes is str or self.fp == sys.stdout:
|
||||
if not py3 or self.fp == sys.stdout:
|
||||
self.fp.write(to_write)
|
||||
else:
|
||||
self.fp.write(bytes(to_write, 'UTF-8'))
|
||||
|
@ -153,6 +154,7 @@ class PSDraw(object):
|
|||
# Copyright (c) Fredrik Lundh 1994.
|
||||
#
|
||||
|
||||
|
||||
EDROFF_PS = """\
|
||||
/S { show } bind def
|
||||
/P { moveto show } bind def
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from PIL._binary import o8
|
||||
from ._binary import o8
|
||||
|
||||
|
||||
##
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue