diff --git a/.github/codeql/codeql-config.yml b/.github/codeql/codeql-config.yml new file mode 100644 index 00000000..0e15a2bc --- /dev/null +++ b/.github/codeql/codeql-config.yml @@ -0,0 +1,10 @@ +# Source - https://stackoverflow.com/a/78835420 +# Posted by sclarson +# Retrieved 2025-11-09, License - CC BY-SA 4.0 + +paths: + - 'src' + - 'js' +paths-ignore: + - 'dist' + - 'build' diff --git a/.github/workflows/build-python.yml b/.github/workflows/build-python.yml new file mode 100644 index 00000000..031b25f7 --- /dev/null +++ b/.github/workflows/build-python.yml @@ -0,0 +1,119 @@ +name: Build and Test Python Bindings + +on: + push: + pull_request: + +jobs: + build-linux: + runs-on: ubuntu-24.04 + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Cache pip packages + uses: actions/cache@v4 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt', '**/setup.py') }} + restore-keys: | + ${{ runner.os }}-pip- + + - name: Install system dependencies + run: | + sudo apt-get update + sudo apt-get install -y libjpeg9 + + - name: Install Python dependencies + run: | + python -m pip install --upgrade pip + pip install numpy setuptools wheel pybind11 pytest pillow + + - name: Update submodules + run: git submodule update --init + + - name: Build the Python bindings + run: | + cd python-bindings + python setup.py bdist_wheel && pip install dist/*.whl + + - name: Test the build + run: | + cd python-bindings + python example.py + + - name: Run unit tests + run: npm run python-tests + + build-windows: + runs-on: windows-2022 + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Cache pip packages + uses: actions/cache@v4 + with: + path: ~\AppData\Local\pip\Cache + key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt', '**/setup.py') }} + restore-keys: | + ${{ runner.os }}-pip- + + - name: Cache vcpkg + uses: actions/cache@v4 + with: + path: | + vcpkg + !vcpkg/.git + key: ${{ runner.os }}-vcpkg-${{ hashFiles('vcpkg.json') }} + + - name: Install vcpkg + run: | + git clone https://github.com/microsoft/vcpkg.git + .\vcpkg\bootstrap-vcpkg.bat + + - name: Install libjpeg-turbo + run: .\vcpkg\vcpkg install libjpeg-turbo + + - name: Install pthreads + run: .\vcpkg\vcpkg install pthreads + + - name: Add vcpkg to path + shell: bash + run: echo "vcpkg/installed/x64-windows/bin" >> $GITHUB_PATH + + - name: Install Python dependencies + run: | + python -m pip install --upgrade pip + pip install numpy setuptools wheel pybind11 pytest pillow + + - name: Update submodules + run: git submodule update --init + + - name: Build the Python bindings + shell: pwsh + run: | + cd python-bindings + python setup.py bdist_wheel + $version = (python setup.py --version).Trim() + pip install dist/artoolkitnft-0.0.10-cp311-cp311-win_amd64.whl + + - name: Test the build + run: | + cd python-bindings + python example.py + + - name: Run unit tests + run: npm run python-tests \ No newline at end of file diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 0690f92b..e9ab0c69 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -65,6 +65,7 @@ jobs: with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} + config-file: ./.github/codeql/codeql-config.yml # If you wish to specify custom queries, you can do so here or in a config file. # By default, queries listed here will override any specified in a config file. # Prefix the list here with "+" to use these queries and those in the config file. diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 00000000..eb041798 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,54 @@ +name: Publish to npm + +on: + push: + tags: + - '*.*.*' # e.g. 1.2.3 + workflow_dispatch: + inputs: + tag: + description: Existing tag to publish (e.g. 1.2.3) + required: true + +permissions: + contents: read + id-token: write # required for npm provenance + +jobs: + publish: + runs-on: ubuntu-24.04 + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 22.15.1 + registry-url: https://registry.npmjs.org + cache: npm + + - name: Install + run: npm ci + + - name: Test + run: npm test + env: + CI: true + + - name: Build + run: npm run build --if-present + + - name: Verify npm auth + run: npm whoami + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + + - name: Publish + run: | + # For public scoped packages add --access public + # For private packages omit --access or use --access restricted + npm publish --provenance --access public + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + diff --git a/.gitignore b/.gitignore index dc1fa37d..2b2e68bf 100644 --- a/.gitignore +++ b/.gitignore @@ -4,5 +4,13 @@ build/*.a node_modules docs/* emscripten/build +python-bindings/build +python-bindings/dist +python-bindings/emscripten +python-bindings/*.so +python-bindings/deps/libjpeg/* +python-bindings/__pycache__ +python-bindings/*.egg-info +python-bindings/src/*.egg-info .vscode .idea \ No newline at end of file diff --git a/emscripten/zlib b/emscripten/zlib index 839cb7d4..cacf7f1d 160000 --- a/emscripten/zlib +++ b/emscripten/zlib @@ -1 +1 @@ -Subproject commit 839cb7d466a0e2973d0789b2905c015b6c11e564 +Subproject commit cacf7f1d4e3d44d871b605da3b647f07d718623f diff --git a/package.json b/package.json index 0cbd74e7..d38db233 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@webarkit/jsartoolkit-nft", - "version": "1.7.6", + "version": "1.7.2", "main": "dist/ARToolkitNFT.js", "types": "types/src/index.d.ts", "description": "Emscripten port of ARToolKit5 to JavaScript. It is a lighter version of Jsartoolkit5 with only NFT markerless support", @@ -72,6 +72,8 @@ "test:es6": "cross-env BUILD_TARGET_ES6=artoolkitNFT_ES6_wasm.js karma start karma-es6.conf.js", "test:es6-simd": "cross-env BUILD_TARGET_ES6=artoolkitNFT_ES6_wasm.simd.js karma start karma-es6.conf.js", "test:all": "npm run test:min && npm run test:debug && npm run test:wasm && npm run test:simd && npm run test:embed-es6 && npm run test:es6 && npm run test:es6-simd", + "build-python-bindings": "cd python-bindings && python setup.py build_ext --inplace", + "python-tests": "cd python-bindings && pytest test_arcontrollerNFT.py", "watch": "./node_modules/.bin/watch 'npm run build' ./js/", "format-check": "prettier --check .", "format": "prettier --write .", diff --git a/python-bindings/ARToolKitNFT_py.cpp b/python-bindings/ARToolKitNFT_py.cpp new file mode 100644 index 00000000..4d5eee93 --- /dev/null +++ b/python-bindings/ARToolKitNFT_py.cpp @@ -0,0 +1,597 @@ +#include "ARToolKitNFT_py.h" +#include + +ARToolKitNFT::ARToolKitNFT() + : id(0), paramLT(nullptr), videoFrame(nullptr), videoFrameSize(0), + videoLuma(nullptr), width(0), height(0), + detectedPage(-2), // -2 Tracking not inited, -1 tracking inited OK, >= 0 + // tracking online on page. +surfaceSetCount(0), // Running NFT marker id +arhandle(nullptr), ar3DHandle(nullptr), +kpmHandle(nullptr, [](KpmHandle*){/* empty deleter */}), // Fix: proper nullptr with deleter +ar2Handle(nullptr), +#if WITH_FILTERING + ftmi(nullptr), filterCutoffFrequency(60.0), filterSampleRate(120.0), +#endif + nearPlane(0.0001), farPlane(1000.0), + patt_id(0) // Running pattern marker id +{ + ARLOGi("init ARToolKitNFT constructor...\n"); +} + +ARToolKitNFT::~ARToolKitNFT() { + teardown(); +} + +void matrixLerp(ARdouble src[3][4], ARdouble dst[3][4], + float interpolationFactor) { + for (auto i = 0; i < 3; i++) { + for (auto j = 0; j < 4; j++) { + dst[i][j] = dst[i][j] + (src[i][j] - dst[i][j]) * interpolationFactor; + } + } +} + +int ARToolKitNFT::passVideoData(py::array_t videoFrame, py::array_t videoLuma) { + auto frame_info = videoFrame.request(); + auto luma_info = videoLuma.request(); + + ARLOGi("Video Frame Debug:\n"); + ARLOGi(" Dimensions: %ld\n", frame_info.ndim); + ARLOGi(" Shape: Total elements: %ld\n", frame_info.shape[0]); + ARLOGi(" Total size: %ld bytes\n", frame_info.size); + ARLOGi(" Item size: %ld bytes\n", frame_info.itemsize); + + ARLOGi("Video Luma Debug:\n"); + ARLOGi(" Dimensions: %ld\n", luma_info.ndim); + ARLOGi(" Shape: Total elements: %ld\n", luma_info.shape[0]); + ARLOGi(" Total size: %ld bytes\n", luma_info.size); + ARLOGi(" Item size: %ld bytes\n", luma_info.itemsize); + + uint8_t* frame_ptr = static_cast(frame_info.ptr); + uint8_t* luma_ptr = static_cast(luma_info.ptr); + + // Copy flattened data + std::copy(frame_ptr, frame_ptr + frame_info.size, this->videoFrame.get()); + std::copy(luma_ptr, luma_ptr + luma_info.size, this->videoLuma.get()); + + ARLOGi("First few frame bytes: %u %u %u %u %u\n", frame_ptr[0], frame_ptr[1], frame_ptr[2], frame_ptr[3], frame_ptr[4]); + ARLOGi("First few luma bytes: %u %u %u %u %u\n", luma_ptr[0], luma_ptr[1], luma_ptr[2], luma_ptr[3], luma_ptr[4]); + + return 0; +} + +py::dict ARToolKitNFT::getNFTMarkerInfo(int markerIndex) { + py::dict NFTMarkerInfo; + py::list pose; + + if (this->surfaceSetCount <= markerIndex) { + return py::dict("error"_a=MARKER_INDEX_OUT_OF_BOUNDS); + } + + float trans[3][4]; + +#if WITH_FILTERING + ARdouble transF[3][4]; + ARdouble transFLerp[3][4]; + memset(transFLerp, 0, 3 * 4 * sizeof(ARdouble)); +#endif + + float err = -1; + if (this->detectedPage == markerIndex) { + int trackResult = + ar2TrackingMod(this->ar2Handle, this->surfaceSet[this->detectedPage], + this->videoFrame.get(), trans, &err); + +#if WITH_FILTERING + std::copy(&trans[0][0], &trans[0][0] + 3 * 4, &transF[0][0]); + + bool reset = (trackResult < 0); + + if (arFilterTransMat(this->ftmi, transF, reset) < 0) { + ARLOGe("arFilterTransMat error with marker %d.", markerIndex); + } + + matrixLerp(transF, transFLerp, 0.95); +#endif + + if (trackResult < 0) { + ARLOGi("Tracking lost. %d", trackResult); + this->detectedPage = -2; + } else { + ARLOGi("Tracked page %d (max %d).\n", + this->surfaceSet[this->detectedPage], this->surfaceSetCount - 1); + } + } + + if (this->detectedPage == markerIndex) { + NFTMarkerInfo["id"] = markerIndex; + NFTMarkerInfo["error"] = err; + NFTMarkerInfo["found"] = 1; +#if WITH_FILTERING + for (auto x = 0; x < 3; x++) { + for (auto y = 0; y < 4; y++) { + pose.append(transFLerp[x][y]); + } + } +#else + for (auto x = 0; x < 3; x++) { + for (auto y = 0; y < 4; y++) { + pose.append(trans[x][y]); + } + } +#endif + NFTMarkerInfo["pose"] = pose; + } else { + NFTMarkerInfo["id"] = markerIndex; + NFTMarkerInfo["error"] = -1; + NFTMarkerInfo["found"] = 0; + for (auto x = 0; x < 3; x++) { + for (auto y = 0; y < 4; y++) { + pose.append(0.0f); + } + } + NFTMarkerInfo["pose"] = pose; + } + + return NFTMarkerInfo; +} + +int ARToolKitNFT::detectNFTMarker() { + + KpmResult *kpmResult = nullptr; + int kpmResultNum = -1; + + if (this->detectedPage == -2) { + ARLOGi("detectedPage = %d\n", this->detectedPage); + kpmMatching(this->kpmHandle.get(), this->videoLuma.get()); + kpmGetResult(this->kpmHandle.get(), &kpmResult, &kpmResultNum); + if (kpmResultNum > 0) { + ARLOGi("kpmMatching found %d results.\n", kpmResultNum); + for (int i = 0; i < kpmResultNum; i++) { + ARLOGi("kpmResult[%d]: pageNo = %d, error = %f, inlierNum = %d, camPoseF = %d\n", + i, + kpmResult[i].pageNo, + kpmResult[i].error, + kpmResult[i].inlierNum, + kpmResult[i].camPoseF); + } + } else { + ARLOGi("No kpmResults found."); + } +#if WITH_FILTERING + this->ftmi = arFilterTransMatInit(this->filterSampleRate, + this->filterCutoffFrequency); +#endif + + for (int i = 0; i < kpmResultNum; i++) { + if (kpmResult[i].camPoseF == 0) { + ARLOGi("kpmResult[i].pageNo = %d\n", kpmResult[i].pageNo); + float trans[3][4]; + this->detectedPage = kpmResult[i].pageNo; + std::copy(&kpmResult[i].camPose[0][0], &kpmResult[i].camPose[0][0] + 3 * 4, &trans[0][0]); + ar2SetInitTrans(this->surfaceSet[this->detectedPage], trans); + } + } + } + return kpmResultNum; +} + +std::unique_ptr ARToolKitNFT::createKpmHandle(ARParamLT *cparamLT) { + KpmHandle* handle = kpmCreateHandle(cparamLT); + if (!handle) { + ARLOGe("Error: kpmCreateHandle returned NULL."); + // Return empty unique_ptr with proper deleter type + return std::unique_ptr(nullptr, [](KpmHandle* p) { + if (p) kpmDeleteHandle(&p); + }); + } + return std::unique_ptr(handle, [](KpmHandle* p) { + if (p) kpmDeleteHandle(&p); + }); +} + +/*int ARToolKitNFT::getKpmImageWidth(KpmHandle *kpmHandle) { + return kpmHandleGetXSize(kpmHandle); +} + +int ARToolKitNFT::getKpmImageHeight(KpmHandle *kpmHandle) { + return kpmHandleGetYSize(kpmHandle); +}*/ + +int ARToolKitNFT::setupAR2() { + AR2HandleT* tempHandle = ar2CreateHandleMod(this->paramLT, this->pixFormat); + if (tempHandle == nullptr) { + ARLOGe("Error: ar2CreateHandle."); + return -1; // Return error code if handle creation failed + } + + // Store the handle + this->ar2Handle = tempHandle; + + // Settings for devices with single-core CPUs. + ar2SetTrackingThresh(this->ar2Handle, 2.0); // Lowering tolerance. + ar2SetSimThresh(this->ar2Handle, 0.3); // Increase similarity tolerance. + ar2SetSearchFeatureNum(this->ar2Handle, 16); + ar2SetSearchSize(this->ar2Handle, 6); + ar2SetTemplateSize1(this->ar2Handle, 6); + ar2SetTemplateSize2(this->ar2Handle, 6); + + // Create KPM handle + this->kpmHandle = createKpmHandle(this->paramLT); + if (!this->kpmHandle) { + ARLOGe("Error creating KPM handle"); + return -1; + } + + return 0; // Success +} + +nftMarker ARToolKitNFT::getNFTData(int index) { + // get marker(s) nft data. + return this->nftMarkers.at(index); +} + +/*********** + * Teardown * + ***********/ + +void ARToolKitNFT::deleteHandle() { + if (this->arhandle != nullptr) { + arPattDetach(this->arhandle); + arDeleteHandle(this->arhandle); + this->arhandle = nullptr; + } + if (this->ar3DHandle != nullptr) { + ar3DDeleteHandle(&(this->ar3DHandle)); + this->ar3DHandle = nullptr; + } + if (this->paramLT != nullptr) { + arParamLTFree(&(this->paramLT)); + this->paramLT = nullptr; + } +} + +int ARToolKitNFT::teardown() { + // Reset unique pointers instead of freeing memory + this->videoFrame.reset(); + this->videoLuma.reset(); + this->videoFrameSize = 0; + + deleteHandle(); + + return 0; +} + +int ARToolKitNFT::setCamera(int id, int cameraID) { + + if (cameraParams.find(cameraID) == cameraParams.end()) { + return -1; + } + + this->param = cameraParams[cameraID]; + + if (this->param.xsize != this->width || this->param.ysize != this->height) { + ARLOGw("*** Camera Parameter resized from %d, %d. ***\n", this->param.xsize, + this->param.ysize); + arParamChangeSize(&(this->param), this->width, this->height, + &(this->param)); + } + + // ARLOGi("*** Camera Parameter ***\n"); + // arParamDisp(&(this->param)); + + deleteHandle(); + + if ((this->paramLT = arParamLTCreate(&(this->param), + AR_PARAM_LT_DEFAULT_OFFSET)) == nullptr) { + ARLOGe("setCamera(): Error: arParamLTCreate.\n"); + return -1; + } + + // ARLOGi("setCamera(): arParamLTCreated\n..%d, %d\n", + // (this->paramLT->param).xsize, (this->paramLT->param).ysize); + + // setup camera + if ((this->arhandle = arCreateHandle(this->paramLT)) == nullptr) { + ARLOGe("setCamera(): Error: arCreateHandle.\n"); + return -1; + } + // AR_DEFAULT_PIXEL_FORMAT + int set = arSetPixelFormat(this->arhandle, this->pixFormat); + + this->ar3DHandle = ar3DCreateHandle(&(this->param)); + if (this->ar3DHandle == nullptr) { + ARLOGe("setCamera(): Error creating 3D handle\n"); + return -1; + } + + arglCameraFrustumRH(&((this->paramLT)->param), this->nearPlane, + this->farPlane, this->cameraLens); + + return 0; +} + +int ARToolKitNFT::loadCamera(std::string cparam_name) { + ARParam param; + if (arParamLoad(cparam_name.c_str(), 1, ¶m) < 0) { + ARLOGe("loadCamera(): Error loading parameter file %s for camera.\n", + cparam_name.c_str()); + return -1; + } + int cameraID = gCameraID++; + cameraParams[cameraID] = param; + + return cameraID; +} + +py::array_t ARToolKitNFT::getCameraLens() { + py::array_t lens({16}); + auto lens_ptr = lens.mutable_data(); + for (int i = 0; i < 16; i++) { + lens_ptr[i] = this->cameraLens[i]; + } + return lens; +} + +/*int ARToolKitNFT::decompressZFT(std::string datasetPathname, std::string tempPathname){ + int response = decompressMarkers(datasetPathname.c_str(), tempPathname.c_str()); + + return 1; +}*/ + +/***************** + * Marker loading * + *****************/ + +std::vector +ARToolKitNFT::addNFTMarkers(std::vector &datasetPathnames) { + + //auto kpmHandle = this->kpmHandle.get(); + + KpmRefDataSet *refDataSet = nullptr; + + if (datasetPathnames.size() >= PAGES_MAX) { + ARLOGe("Error exceed maximum pages.\n"); + exit(-1); + } + + std::vector markerIds = {}; + + for (int i = 0; i < datasetPathnames.size(); i++) { + ARLOGi("datasetPathnames size: %i\n", datasetPathnames.size()); + ARLOGi("add NFT marker-> '%s'\n", datasetPathnames[i].c_str()); + + const char *datasetPathname = datasetPathnames[i].c_str(); + int pageNo = i; + markerIds.push_back(i); + + // Load KPM data. + KpmRefDataSet *refDataSet2; + ARLOGi("Reading %s.fset3\n", datasetPathname); + if (kpmLoadRefDataSet(datasetPathname, "fset3", &refDataSet2) < 0) { + ARLOGe("Error reading KPM data from %s.fset3\n", datasetPathname); + return {}; + } + ARLOGi("Assigned page no. %d.\n", pageNo); + if (kpmChangePageNoOfRefDataSet(refDataSet2, KpmChangePageNoAllPages, + pageNo) < 0) { + ARLOGe("Error: kpmChangePageNoOfRefDataSet\n"); + return {}; + } + if (kpmMergeRefDataSet(&refDataSet, &refDataSet2) < 0) { + ARLOGe("Error: kpmMergeRefDataSet\n"); + return {}; + } + ARLOGi("Done.\n"); + + // Load AR2 data. + ARLOGi("Reading %s.fset\n", datasetPathname); + + if ((this->surfaceSet[i] = + ar2ReadSurfaceSet(datasetPathname, "fset", nullptr)) == nullptr) { + ARLOGe("Error reading data from %s.fset\n", datasetPathname); + return {}; + } + + int surfaceSetCount = this->surfaceSetCount; + int numIset = this->surfaceSet[i]->surface[0].imageSet->num; + this->nft.width_NFT = + this->surfaceSet[i]->surface[0].imageSet->scale[0]->xsize; + this->nft.height_NFT = + this->surfaceSet[i]->surface[0].imageSet->scale[0]->ysize; + this->nft.dpi_NFT = this->surfaceSet[i]->surface[0].imageSet->scale[0]->dpi; + + ARLOGi("NFT num. of ImageSet: %i\n", numIset); + ARLOGi("NFT marker width: %i\n", this->nft.width_NFT); + ARLOGi("NFT marker height: %i\n", this->nft.height_NFT); + ARLOGi("NFT marker dpi: %i\n", this->nft.dpi_NFT); + + this->nft.id_NFT = i; + this->nft.width_NFT = this->nft.width_NFT; + this->nft.height_NFT = this->nft.height_NFT; + this->nft.dpi_NFT = this->nft.dpi_NFT; + this->nftMarkers.push_back(this->nft); + + ARLOGi("Done.\n"); + surfaceSetCount++; + } + + if (kpmSetRefDataSet(this->kpmHandle.get(), refDataSet) < 0) { + ARLOGe("Error: kpmSetRefDataSet\n"); + return {}; + } + kpmDeleteRefDataSet(&refDataSet); + + ARLOGi("Loading of NFT data complete.\n"); + + this->surfaceSetCount += markerIds.size(); + + return markerIds; +} + +/********************** + * Setters and getters * + **********************/ + +/*************** + * Set Log Level + ****************/ +void ARToolKitNFT::setLogLevel(int level) { this->arLogLevel = level; } + +int ARToolKitNFT::getLogLevel() { return this->arLogLevel; } + +void ARToolKitNFT::setProjectionNearPlane(const ARdouble projectionNearPlane) { + this->nearPlane = projectionNearPlane; +} + +ARdouble ARToolKitNFT::getProjectionNearPlane() { return this->nearPlane; } + +void ARToolKitNFT::setProjectionFarPlane(const ARdouble projectionFarPlane) { + this->farPlane = projectionFarPlane; +} + +ARdouble ARToolKitNFT::getProjectionFarPlane() { return this->farPlane; } + +void ARToolKitNFT::setThreshold(int threshold) { + if (threshold < 0 || threshold > 255) + return; + if (arSetLabelingThresh(this->arhandle, threshold) == 0) { + ARLOGi("Threshold set to %d\n", threshold); + }; + // default 100 + // arSetLabelingThreshMode + // AR_LABELING_THRESH_MODE_MANUAL, AR_LABELING_THRESH_MODE_AUTO_MEDIAN, + // AR_LABELING_THRESH_MODE_AUTO_OTSU, AR_LABELING_THRESH_MODE_AUTO_ADAPTIVE +} + +int ARToolKitNFT::getThreshold() { + int threshold; + if (arGetLabelingThresh(this->arhandle, &threshold) == 0) { + return threshold; + }; + + return -1; +} + +void ARToolKitNFT::setThresholdMode(int mode) { + AR_LABELING_THRESH_MODE thresholdMode = (AR_LABELING_THRESH_MODE)mode; + + if (arSetLabelingThreshMode(this->arhandle, thresholdMode) == 0) { + ARLOGi("Threshold mode set to %d\n", (int)thresholdMode); + } +} + +int ARToolKitNFT::getThresholdMode() { + AR_LABELING_THRESH_MODE thresholdMode; + + if (arGetLabelingThreshMode(this->arhandle, &thresholdMode) == 0) { + return thresholdMode; + } + + return -1; +} + +int ARToolKitNFT::setDebugMode(int enable) { + arSetDebugMode(this->arhandle, enable ? AR_DEBUG_ENABLE : AR_DEBUG_DISABLE); + ARLOGi("Debug mode set to %s\n", enable ? "on." : "off."); + + return enable; +} + +int ARToolKitNFT::getProcessingImage() { + + return reinterpret_cast(this->arhandle->labelInfo.bwImage); +} + +int ARToolKitNFT::getDebugMode() { + int enable; + + arGetDebugMode(this->arhandle, &enable); + return enable; +} + +void ARToolKitNFT::setImageProcMode(int mode) { + int imageProcMode = mode; + if (arSetImageProcMode(this->arhandle, mode) == 0) { + ARLOGi("Image proc. mode set to %d.\n", imageProcMode); + } +} + +int ARToolKitNFT::getImageProcMode() { + int imageProcMode; + if (arGetImageProcMode(this->arhandle, &imageProcMode) == 0) { + return imageProcMode; + } + + return -1; +} + +int ARToolKitNFT::setup(int width, int height, int cameraID) { + int id = gARControllerID++; + this->id = id; + + this->width = width; + this->height = height; + + this->videoFrameSize = width * height * 4 * sizeof(ARUint8); + this->videoFrame = std::make_unique(this->videoFrameSize); + this->videoLuma = std::make_unique(this->videoFrameSize / 4); + + setCamera(id, cameraID); + + ARLOGi("Allocated videoFrameSize %d\n", this->videoFrameSize); + + return this->id; +} + +PYBIND11_MODULE(artoolkitnft_core, m) { + m.doc() = "ARToolKitNFT Python bindings"; + py::class_(m, "nftMarker") + .def(py::init<>()) + .def_readwrite("id_NFT", &nftMarker::id_NFT) + .def_readwrite("width_NFT", &nftMarker::width_NFT) + .def_readwrite("height_NFT", &nftMarker::height_NFT) + .def_readwrite("dpi_NFT", &nftMarker::dpi_NFT) + .def("__repr__", + [](const nftMarker &n) { + return ""; + }); + + + py::class_(m, "ARToolKitNFT") + .def(py::init<>()) + .def("passVideoData", &ARToolKitNFT::passVideoData) + .def("detectNFTMarker", &ARToolKitNFT::detectNFTMarker) + .def("getNFTMarker", &ARToolKitNFT::getNFTMarkerInfo) + //.def("getKpmImageWidth", &ARToolKitNFT::getKpmImageWidth) + //.def("getKpmImageHeight", &ARToolKitNFT::getKpmImageHeight) + .def("setupAR2", &ARToolKitNFT::setupAR2) + .def("getNFTData", &ARToolKitNFT::getNFTData) + .def("setLogLevel", &ARToolKitNFT::setLogLevel) + .def("getLogLevel", &ARToolKitNFT::getLogLevel) + .def("teardown", &ARToolKitNFT::teardown) + .def("loadCamera", &ARToolKitNFT::loadCamera) + .def("getCameraLens", &ARToolKitNFT::getCameraLens) + .def("addNFTMarkers", &ARToolKitNFT::addNFTMarkers) + .def("setProjectionNearPlane", &ARToolKitNFT::setProjectionNearPlane) + .def("getProjectionNearPlane", &ARToolKitNFT::getProjectionNearPlane) + .def("setProjectionFarPlane", &ARToolKitNFT::setProjectionFarPlane) + .def("getProjectionFarPlane", &ARToolKitNFT::getProjectionFarPlane) + .def("setThreshold", &ARToolKitNFT::setThreshold) + .def("getThreshold", &ARToolKitNFT::getThreshold) + .def("setThresholdMode", &ARToolKitNFT::setThresholdMode) + .def("getThresholdMode", &ARToolKitNFT::getThresholdMode) + .def("setDebugMode", &ARToolKitNFT::setDebugMode) + .def("getProcessingImage", &ARToolKitNFT::getProcessingImage) + .def("getDebugMode", &ARToolKitNFT::getDebugMode) + .def("setImageProcMode", &ARToolKitNFT::setImageProcMode) + .def("getImageProcMode", &ARToolKitNFT::getImageProcMode) + .def("setup", &ARToolKitNFT::setup); + + // Define the NFT_MARKER constant + m.attr("NFT_MARKER") = py::int_(0); // Replace 1 with the actual value of NFT_MARKER +} diff --git a/python-bindings/ARToolKitNFT_py.h b/python-bindings/ARToolKitNFT_py.h new file mode 100644 index 00000000..1a60d5ff --- /dev/null +++ b/python-bindings/ARToolKitNFT_py.h @@ -0,0 +1,126 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +//#include +#include "trackingMod.h" +//#include "markerDecompress.h" + +namespace py = pybind11; +using namespace pybind11::literals; + +const int PAGES_MAX = 20; // Maximum number of pages expected. You can change this down (to save memory) or up (to accomodate more pages.) + +struct nftMarker +{ + int id_NFT; + int width_NFT; + int height_NFT; + int dpi_NFT; +}; + +static int gARControllerID = 0; +static int gCameraID = 0; + +static int MARKER_INDEX_OUT_OF_BOUNDS = -3; + +std::unordered_map cameraParams; + +class ARToolKitNFT +{ +public: + ARToolKitNFT(); + ~ARToolKitNFT(); + int passVideoData(py::array_t videoFrame, py::array_t videoLuma); + py::dict getNFTMarkerInfo(int markerIndex); + + int detectNFTMarker(); + //int getKpmImageWidth(KpmHandle *kpmHandle); + //int getKpmImageHeight(KpmHandle *kpmHandle); + int setupAR2(); + nftMarker getNFTData(int index); + + void setLogLevel(int level); + int getLogLevel(); + + int teardown(); + int loadCamera(std::string cparam_name); + int setCamera(int id, int cameraID); + //emscripten::val getCameraLens(); + py::array_t getCameraLens(); + //int decompressZFT(std::string datasetPathname, std::string tempPathname); + std::vector addNFTMarkers(std::vector &datasetPathnames); + + // setters and getters + void setProjectionNearPlane(const ARdouble projectionNearPlane); + ARdouble getProjectionNearPlane(); + void setProjectionFarPlane(const ARdouble projectionFarPlane); + ARdouble getProjectionFarPlane(); + void setThreshold(int threshold); + int getThreshold(); + void setThresholdMode(int mode); + int getThresholdMode(); + int setDebugMode(int enable); + int getProcessingImage(); + int getDebugMode(); + void setImageProcMode(int mode); + int getImageProcMode(); + int setup(int width, int height, int cameraID); + +private: + std::unique_ptr createKpmHandle(ARParamLT *cparamLT); + void deleteHandle(); + + int id; + + ARParam param; + ARParamLT *paramLT; + + std::unique_ptr videoFrame; + int videoFrameSize; + std::unique_ptr videoLuma; + + int width; + int height; + + ARHandle *arhandle; + AR3DHandle *ar3DHandle; + + std::unique_ptr kpmHandle; + AR2HandleT *ar2Handle; + +#if WITH_FILTERING + ARFilterTransMatInfo *ftmi; + ARdouble filterCutoffFrequency; + ARdouble filterSampleRate; +#endif + + int arLogLevel = AR_LOG_LEVEL_INFO; + int detectedPage; + + int surfaceSetCount; + AR2SurfaceSetT *surfaceSet[PAGES_MAX]; + std::unordered_map surfaceSets; + // nftMarker struct inside arController + nftMarker nft; + std::vector nftMarkers; + + ARdouble nearPlane; + ARdouble farPlane; + + int patt_id; + + ARdouble cameraLens[16]; + AR_PIXEL_FORMAT pixFormat = AR_PIXEL_FORMAT_RGBA; +}; \ No newline at end of file diff --git a/python-bindings/LICENSE b/python-bindings/LICENSE new file mode 100644 index 00000000..153d416d --- /dev/null +++ b/python-bindings/LICENSE @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. \ No newline at end of file diff --git a/python-bindings/README.md b/python-bindings/README.md new file mode 100644 index 00000000..65a50ef8 --- /dev/null +++ b/python-bindings/README.md @@ -0,0 +1,46 @@ +# ARToolKitNFT for python + +This is a python binding for WebARKitLib library. It is based on the WebARKitLib library and provides a python interface to the library. +For now you can install the package only with testPyPi: pip install -i https://test.pypi.org/simple/ artoolkitnft + +## Publishing the Package to TestPyPI (Linux) + +To publish the package to TestPyPI on a Linux system, follow these steps: + +1. **Install the required tools**: + Ensure you have `setuptools`, `wheel`, and `twine` installed. You can install them using pip: + ```bash + pip install --upgrade setuptools wheel twine + ``` + +2. **Build the wheel**: + Navigate to the directory containing your `setup.py` file and run the following command to build the wheel: + ```bash + python setup.py sdist bdist_wheel --plat-name manylinux2014_x86_64 + ``` + +3. **Check the wheel file**: + Verify that the wheel file has the correct platform tag. You can use the `wheel` tool to inspect the wheel file: + ```bash + pip install wheel + wheel unpack dist/artoolkitnft-0.0.10-cp38-cp38-manylinux2014_x86_64.whl + ``` + +4. **Upload the wheel to TestPyPI**: + Use `twine` to upload the wheel to TestPyPI. You will need your TestPyPI credentials for this step. + ```bash + twine upload --repository-url https://test.pypi.org/legacy/ dist/* + ``` + +Here is a summary of the commands you need to run: + +```bash +pip install --upgrade setuptools wheel twine +python setup.py sdist bdist_wheel --plat-name manylinux2014_x86_64 +pip install wheel +wheel unpack dist/artoolkitnft-0.0.10-cp38-cp38-manylinux2014_x86_64.whl +twine upload --repository-url https://test.pypi.org/legacy/ dist/* +``` + +Make sure you replace `dist/artoolkitnft-0.0.10-cp38-cp38-manylinux2014_x86_64.whl` with the actual path to your wheel file if it's different. +``` diff --git a/python-bindings/deps/include/jconfig.h b/python-bindings/deps/include/jconfig.h new file mode 100644 index 00000000..5486de08 --- /dev/null +++ b/python-bindings/deps/include/jconfig.h @@ -0,0 +1,33 @@ +#define JPEG_LIB_VERSION 62 +#define LIBJPEG_TURBO_VERSION 2.1.3 +#define LIBJPEG_TURBO_VERSION_NUMBER 2001003 + +#define C_ARITH_CODING_SUPPORTED +#define D_ARITH_CODING_SUPPORTED +/* #undef MEM_SRCDST_SUPPORTED */ +#define WITH_SIMD + +#define BITS_IN_JSAMPLE 8 /* use 8 or 12 */ + +#define HAVE_STDDEF_H +#define HAVE_STDLIB_H +#undef NEED_SYS_TYPES_H +#undef NEED_BSD_STRINGS + +#define HAVE_UNSIGNED_CHAR +#define HAVE_UNSIGNED_SHORT +#undef INCOMPLETE_TYPES_BROKEN +#undef RIGHT_SHIFT_IS_UNSIGNED + +/* Define "boolean" as unsigned char, not int, per Windows custom */ +#ifndef __RPCNDR_H__ /* don't conflict if rpcndr.h already read */ +typedef unsigned char boolean; +#endif +#define HAVE_BOOLEAN /* prevent jmorecfg.h from redefining it */ + +/* Define "INT32" as int, not long, per Windows custom */ +#if !(defined(_BASETSD_H_) || defined(_BASETSD_H)) /* don't conflict if basetsd.h already read */ +typedef short INT16; +typedef signed int INT32; +#endif +#define XMD_H /* prevent jmorecfg.h from redefining it */ diff --git a/python-bindings/deps/include/jmorecfg.h b/python-bindings/deps/include/jmorecfg.h new file mode 100644 index 00000000..b33a9919 --- /dev/null +++ b/python-bindings/deps/include/jmorecfg.h @@ -0,0 +1,382 @@ +/* + * jmorecfg.h + * + * This file was part of the Independent JPEG Group's software: + * Copyright (C) 1991-1997, Thomas G. Lane. + * Modified 1997-2009 by Guido Vollbeding. + * libjpeg-turbo Modifications: + * Copyright (C) 2009, 2011, 2014-2015, 2018, 2020, D. R. Commander. + * For conditions of distribution and use, see the accompanying README.ijg + * file. + * + * This file contains additional configuration options that customize the + * JPEG software for special applications or support machine-dependent + * optimizations. Most users will not need to touch this file. + */ + + +/* + * Maximum number of components (color channels) allowed in JPEG image. + * To meet the letter of Rec. ITU-T T.81 | ISO/IEC 10918-1, set this to 255. + * However, darn few applications need more than 4 channels (maybe 5 for CMYK + + * alpha mask). We recommend 10 as a reasonable compromise; use 4 if you are + * really short on memory. (Each allowed component costs a hundred or so + * bytes of storage, whether actually used in an image or not.) + */ + +#define MAX_COMPONENTS 10 /* maximum number of image components */ + + +/* + * Basic data types. + * You may need to change these if you have a machine with unusual data + * type sizes; for example, "char" not 8 bits, "short" not 16 bits, + * or "long" not 32 bits. We don't care whether "int" is 16 or 32 bits, + * but it had better be at least 16. + */ + +/* Representation of a single sample (pixel element value). + * We frequently allocate large arrays of these, so it's important to keep + * them small. But if you have memory to burn and access to char or short + * arrays is very slow on your hardware, you might want to change these. + */ + +#if BITS_IN_JSAMPLE == 8 +/* JSAMPLE should be the smallest type that will hold the values 0..255. + */ + +typedef unsigned char JSAMPLE; +#define GETJSAMPLE(value) ((int)(value)) + +#define MAXJSAMPLE 255 +#define CENTERJSAMPLE 128 + +#endif /* BITS_IN_JSAMPLE == 8 */ + + +#if BITS_IN_JSAMPLE == 12 +/* JSAMPLE should be the smallest type that will hold the values 0..4095. + * On nearly all machines "short" will do nicely. + */ + +typedef short JSAMPLE; +#define GETJSAMPLE(value) ((int)(value)) + +#define MAXJSAMPLE 4095 +#define CENTERJSAMPLE 2048 + +#endif /* BITS_IN_JSAMPLE == 12 */ + + +/* Representation of a DCT frequency coefficient. + * This should be a signed value of at least 16 bits; "short" is usually OK. + * Again, we allocate large arrays of these, but you can change to int + * if you have memory to burn and "short" is really slow. + */ + +typedef short JCOEF; + + +/* Compressed datastreams are represented as arrays of JOCTET. + * These must be EXACTLY 8 bits wide, at least once they are written to + * external storage. Note that when using the stdio data source/destination + * managers, this is also the data type passed to fread/fwrite. + */ + +typedef unsigned char JOCTET; +#define GETJOCTET(value) (value) + + +/* These typedefs are used for various table entries and so forth. + * They must be at least as wide as specified; but making them too big + * won't cost a huge amount of memory, so we don't provide special + * extraction code like we did for JSAMPLE. (In other words, these + * typedefs live at a different point on the speed/space tradeoff curve.) + */ + +/* UINT8 must hold at least the values 0..255. */ + +typedef unsigned char UINT8; + +/* UINT16 must hold at least the values 0..65535. */ + +typedef unsigned short UINT16; + +/* INT16 must hold at least the values -32768..32767. */ + +#ifndef XMD_H /* X11/xmd.h correctly defines INT16 */ +typedef short INT16; +#endif + +/* INT32 must hold at least signed 32-bit values. + * + * NOTE: The INT32 typedef dates back to libjpeg v5 (1994.) Integers were + * sometimes 16-bit back then (MS-DOS), which is why INT32 is typedef'd to + * long. It also wasn't common (or at least as common) in 1994 for INT32 to be + * defined by platform headers. Since then, however, INT32 is defined in + * several other common places: + * + * Xmd.h (X11 header) typedefs INT32 to int on 64-bit platforms and long on + * 32-bit platforms (i.e always a 32-bit signed type.) + * + * basetsd.h (Win32 header) typedefs INT32 to int (always a 32-bit signed type + * on modern platforms.) + * + * qglobal.h (Qt header) typedefs INT32 to int (always a 32-bit signed type on + * modern platforms.) + * + * This is a recipe for conflict, since "long" and "int" aren't always + * compatible types. Since the definition of INT32 has technically been part + * of the libjpeg API for more than 20 years, we can't remove it, but we do not + * use it internally any longer. We instead define a separate type (JLONG) + * for internal use, which ensures that internal behavior will always be the + * same regardless of any external headers that may be included. + */ + +#ifndef XMD_H /* X11/xmd.h correctly defines INT32 */ +#ifndef _BASETSD_H_ /* Microsoft defines it in basetsd.h */ +#ifndef _BASETSD_H /* MinGW is slightly different */ +#ifndef QGLOBAL_H /* Qt defines it in qglobal.h */ +typedef long INT32; +#endif +#endif +#endif +#endif + +/* Datatype used for image dimensions. The JPEG standard only supports + * images up to 64K*64K due to 16-bit fields in SOF markers. Therefore + * "unsigned int" is sufficient on all machines. However, if you need to + * handle larger images and you don't mind deviating from the spec, you + * can change this datatype. (Note that changing this datatype will + * potentially require modifying the SIMD code. The x86-64 SIMD extensions, + * in particular, assume a 32-bit JDIMENSION.) + */ + +typedef unsigned int JDIMENSION; + +#define JPEG_MAX_DIMENSION 65500L /* a tad under 64K to prevent overflows */ + + +/* These macros are used in all function definitions and extern declarations. + * You could modify them if you need to change function linkage conventions; + * in particular, you'll need to do that to make the library a Windows DLL. + * Another application is to make all functions global for use with debuggers + * or code profilers that require it. + */ + +/* a function called through method pointers: */ +#define METHODDEF(type) static type +/* a function used only in its module: */ +#define LOCAL(type) static type +/* a function referenced thru EXTERNs: */ +#define GLOBAL(type) type +/* a reference to a GLOBAL function: */ +#define EXTERN(type) extern type + + +/* Originally, this macro was used as a way of defining function prototypes + * for both modern compilers as well as older compilers that did not support + * prototype parameters. libjpeg-turbo has never supported these older, + * non-ANSI compilers, but the macro is still included because there is some + * software out there that uses it. + */ + +#define JMETHOD(type, methodname, arglist) type (*methodname) arglist + + +/* libjpeg-turbo no longer supports platforms that have far symbols (MS-DOS), + * but again, some software relies on this macro. + */ + +#undef FAR +#define FAR + + +/* + * On a few systems, type boolean and/or its values FALSE, TRUE may appear + * in standard header files. Or you may have conflicts with application- + * specific header files that you want to include together with these files. + * Defining HAVE_BOOLEAN before including jpeglib.h should make it work. + */ + +#ifndef HAVE_BOOLEAN +typedef int boolean; +#endif +#ifndef FALSE /* in case these macros already exist */ +#define FALSE 0 /* values of boolean */ +#endif +#ifndef TRUE +#define TRUE 1 +#endif + + +/* + * The remaining options affect code selection within the JPEG library, + * but they don't need to be visible to most applications using the library. + * To minimize application namespace pollution, the symbols won't be + * defined unless JPEG_INTERNALS or JPEG_INTERNAL_OPTIONS has been defined. + */ + +#ifdef JPEG_INTERNALS +#define JPEG_INTERNAL_OPTIONS +#endif + +#ifdef JPEG_INTERNAL_OPTIONS + + +/* + * These defines indicate whether to include various optional functions. + * Undefining some of these symbols will produce a smaller but less capable + * library. Note that you can leave certain source files out of the + * compilation/linking process if you've #undef'd the corresponding symbols. + * (You may HAVE to do that if your compiler doesn't like null source files.) + */ + +/* Capability options common to encoder and decoder: */ + +#define DCT_ISLOW_SUPPORTED /* accurate integer method */ +#define DCT_IFAST_SUPPORTED /* less accurate int method [legacy feature] */ +#define DCT_FLOAT_SUPPORTED /* floating-point method [legacy feature] */ + +/* Encoder capability options: */ + +#define C_MULTISCAN_FILES_SUPPORTED /* Multiple-scan JPEG files? */ +#define C_PROGRESSIVE_SUPPORTED /* Progressive JPEG? (Requires MULTISCAN)*/ +#define ENTROPY_OPT_SUPPORTED /* Optimization of entropy coding parms? */ +/* Note: if you selected 12-bit data precision, it is dangerous to turn off + * ENTROPY_OPT_SUPPORTED. The standard Huffman tables are only good for 8-bit + * precision, so jchuff.c normally uses entropy optimization to compute + * usable tables for higher precision. If you don't want to do optimization, + * you'll have to supply different default Huffman tables. + * The exact same statements apply for progressive JPEG: the default tables + * don't work for progressive mode. (This may get fixed, however.) + */ +#define INPUT_SMOOTHING_SUPPORTED /* Input image smoothing option? */ + +/* Decoder capability options: */ + +#define D_MULTISCAN_FILES_SUPPORTED /* Multiple-scan JPEG files? */ +#define D_PROGRESSIVE_SUPPORTED /* Progressive JPEG? (Requires MULTISCAN)*/ +#define SAVE_MARKERS_SUPPORTED /* jpeg_save_markers() needed? */ +#define BLOCK_SMOOTHING_SUPPORTED /* Block smoothing? (Progressive only) */ +#define IDCT_SCALING_SUPPORTED /* Output rescaling via IDCT? */ +#undef UPSAMPLE_SCALING_SUPPORTED /* Output rescaling at upsample stage? */ +#define UPSAMPLE_MERGING_SUPPORTED /* Fast path for sloppy upsampling? */ +#define QUANT_1PASS_SUPPORTED /* 1-pass color quantization? */ +#define QUANT_2PASS_SUPPORTED /* 2-pass color quantization? */ + +/* more capability options later, no doubt */ + + +/* + * The RGB_RED, RGB_GREEN, RGB_BLUE, and RGB_PIXELSIZE macros are a vestigial + * feature of libjpeg. The idea was that, if an application developer needed + * to compress from/decompress to a BGR/BGRX/RGBX/XBGR/XRGB buffer, they could + * change these macros, rebuild libjpeg, and link their application statically + * with it. In reality, few people ever did this, because there were some + * severe restrictions involved (cjpeg and djpeg no longer worked properly, + * compressing/decompressing RGB JPEGs no longer worked properly, and the color + * quantizer wouldn't work with pixel sizes other than 3.) Furthermore, since + * all of the O/S-supplied versions of libjpeg were built with the default + * values of RGB_RED, RGB_GREEN, RGB_BLUE, and RGB_PIXELSIZE, many applications + * have come to regard these values as immutable. + * + * The libjpeg-turbo colorspace extensions provide a much cleaner way of + * compressing from/decompressing to buffers with arbitrary component orders + * and pixel sizes. Thus, we do not support changing the values of RGB_RED, + * RGB_GREEN, RGB_BLUE, or RGB_PIXELSIZE. In addition to the restrictions + * listed above, changing these values will also break the SIMD extensions and + * the regression tests. + */ + +#define RGB_RED 0 /* Offset of Red in an RGB scanline element */ +#define RGB_GREEN 1 /* Offset of Green */ +#define RGB_BLUE 2 /* Offset of Blue */ +#define RGB_PIXELSIZE 3 /* JSAMPLEs per RGB scanline element */ + +#define JPEG_NUMCS 17 + +#define EXT_RGB_RED 0 +#define EXT_RGB_GREEN 1 +#define EXT_RGB_BLUE 2 +#define EXT_RGB_PIXELSIZE 3 + +#define EXT_RGBX_RED 0 +#define EXT_RGBX_GREEN 1 +#define EXT_RGBX_BLUE 2 +#define EXT_RGBX_PIXELSIZE 4 + +#define EXT_BGR_RED 2 +#define EXT_BGR_GREEN 1 +#define EXT_BGR_BLUE 0 +#define EXT_BGR_PIXELSIZE 3 + +#define EXT_BGRX_RED 2 +#define EXT_BGRX_GREEN 1 +#define EXT_BGRX_BLUE 0 +#define EXT_BGRX_PIXELSIZE 4 + +#define EXT_XBGR_RED 3 +#define EXT_XBGR_GREEN 2 +#define EXT_XBGR_BLUE 1 +#define EXT_XBGR_PIXELSIZE 4 + +#define EXT_XRGB_RED 1 +#define EXT_XRGB_GREEN 2 +#define EXT_XRGB_BLUE 3 +#define EXT_XRGB_PIXELSIZE 4 + +static const int rgb_red[JPEG_NUMCS] = { + -1, -1, RGB_RED, -1, -1, -1, EXT_RGB_RED, EXT_RGBX_RED, + EXT_BGR_RED, EXT_BGRX_RED, EXT_XBGR_RED, EXT_XRGB_RED, + EXT_RGBX_RED, EXT_BGRX_RED, EXT_XBGR_RED, EXT_XRGB_RED, + -1 +}; + +static const int rgb_green[JPEG_NUMCS] = { + -1, -1, RGB_GREEN, -1, -1, -1, EXT_RGB_GREEN, EXT_RGBX_GREEN, + EXT_BGR_GREEN, EXT_BGRX_GREEN, EXT_XBGR_GREEN, EXT_XRGB_GREEN, + EXT_RGBX_GREEN, EXT_BGRX_GREEN, EXT_XBGR_GREEN, EXT_XRGB_GREEN, + -1 +}; + +static const int rgb_blue[JPEG_NUMCS] = { + -1, -1, RGB_BLUE, -1, -1, -1, EXT_RGB_BLUE, EXT_RGBX_BLUE, + EXT_BGR_BLUE, EXT_BGRX_BLUE, EXT_XBGR_BLUE, EXT_XRGB_BLUE, + EXT_RGBX_BLUE, EXT_BGRX_BLUE, EXT_XBGR_BLUE, EXT_XRGB_BLUE, + -1 +}; + +static const int rgb_pixelsize[JPEG_NUMCS] = { + -1, -1, RGB_PIXELSIZE, -1, -1, -1, EXT_RGB_PIXELSIZE, EXT_RGBX_PIXELSIZE, + EXT_BGR_PIXELSIZE, EXT_BGRX_PIXELSIZE, EXT_XBGR_PIXELSIZE, EXT_XRGB_PIXELSIZE, + EXT_RGBX_PIXELSIZE, EXT_BGRX_PIXELSIZE, EXT_XBGR_PIXELSIZE, EXT_XRGB_PIXELSIZE, + -1 +}; + +/* Definitions for speed-related optimizations. */ + +/* On some machines (notably 68000 series) "int" is 32 bits, but multiplying + * two 16-bit shorts is faster than multiplying two ints. Define MULTIPLIER + * as short on such a machine. MULTIPLIER must be at least 16 bits wide. + */ + +#ifndef MULTIPLIER +#ifndef WITH_SIMD +#define MULTIPLIER int /* type for fastest integer multiply */ +#else +#define MULTIPLIER short /* prefer 16-bit with SIMD for parellelism */ +#endif +#endif + + +/* FAST_FLOAT should be either float or double, whichever is done faster + * by your compiler. (Note that this type is only used in the floating point + * DCT routines, so it only matters if you've defined DCT_FLOAT_SUPPORTED.) + */ + +#ifndef FAST_FLOAT +#define FAST_FLOAT float +#endif + +#endif /* JPEG_INTERNAL_OPTIONS */ diff --git a/python-bindings/deps/include/jpeglib.h b/python-bindings/deps/include/jpeglib.h new file mode 100644 index 00000000..d7664f06 --- /dev/null +++ b/python-bindings/deps/include/jpeglib.h @@ -0,0 +1,1132 @@ +/* + * jpeglib.h + * + * This file was part of the Independent JPEG Group's software: + * Copyright (C) 1991-1998, Thomas G. Lane. + * Modified 2002-2009 by Guido Vollbeding. + * libjpeg-turbo Modifications: + * Copyright (C) 2009-2011, 2013-2014, 2016-2017, 2020, D. R. Commander. + * Copyright (C) 2015, Google, Inc. + * For conditions of distribution and use, see the accompanying README.ijg + * file. + * + * This file defines the application interface for the JPEG library. + * Most applications using the library need only include this file, + * and perhaps jerror.h if they want to know the exact error codes. + */ + +#ifndef JPEGLIB_H +#define JPEGLIB_H + +/* + * First we include the configuration files that record how this + * installation of the JPEG library is set up. jconfig.h can be + * generated automatically for many systems. jmorecfg.h contains + * manual configuration options that most people need not worry about. + */ + +#ifndef JCONFIG_INCLUDED /* in case jinclude.h already did */ +#include "jconfig.h" /* widely used configuration options */ +#endif +#include "jmorecfg.h" /* seldom changed options */ + + +#ifdef __cplusplus +#ifndef DONT_USE_EXTERN_C +extern "C" { +#endif +#endif + + +/* Various constants determining the sizes of things. + * All of these are specified by the JPEG standard, so don't change them + * if you want to be compatible. + */ + +#define DCTSIZE 8 /* The basic DCT block is 8x8 samples */ +#define DCTSIZE2 64 /* DCTSIZE squared; # of elements in a block */ +#define NUM_QUANT_TBLS 4 /* Quantization tables are numbered 0..3 */ +#define NUM_HUFF_TBLS 4 /* Huffman tables are numbered 0..3 */ +#define NUM_ARITH_TBLS 16 /* Arith-coding tables are numbered 0..15 */ +#define MAX_COMPS_IN_SCAN 4 /* JPEG limit on # of components in one scan */ +#define MAX_SAMP_FACTOR 4 /* JPEG limit on sampling factors */ +/* Unfortunately, some bozo at Adobe saw no reason to be bound by the standard; + * the PostScript DCT filter can emit files with many more than 10 blocks/MCU. + * If you happen to run across such a file, you can up D_MAX_BLOCKS_IN_MCU + * to handle it. We even let you do this from the jconfig.h file. However, + * we strongly discourage changing C_MAX_BLOCKS_IN_MCU; just because Adobe + * sometimes emits noncompliant files doesn't mean you should too. + */ +#define C_MAX_BLOCKS_IN_MCU 10 /* compressor's limit on blocks per MCU */ +#ifndef D_MAX_BLOCKS_IN_MCU +#define D_MAX_BLOCKS_IN_MCU 10 /* decompressor's limit on blocks per MCU */ +#endif + + +/* Data structures for images (arrays of samples and of DCT coefficients). + */ + +typedef JSAMPLE *JSAMPROW; /* ptr to one image row of pixel samples. */ +typedef JSAMPROW *JSAMPARRAY; /* ptr to some rows (a 2-D sample array) */ +typedef JSAMPARRAY *JSAMPIMAGE; /* a 3-D sample array: top index is color */ + +typedef JCOEF JBLOCK[DCTSIZE2]; /* one block of coefficients */ +typedef JBLOCK *JBLOCKROW; /* pointer to one row of coefficient blocks */ +typedef JBLOCKROW *JBLOCKARRAY; /* a 2-D array of coefficient blocks */ +typedef JBLOCKARRAY *JBLOCKIMAGE; /* a 3-D array of coefficient blocks */ + +typedef JCOEF *JCOEFPTR; /* useful in a couple of places */ + + +/* Types for JPEG compression parameters and working tables. */ + + +/* DCT coefficient quantization tables. */ + +typedef struct { + /* This array gives the coefficient quantizers in natural array order + * (not the zigzag order in which they are stored in a JPEG DQT marker). + * CAUTION: IJG versions prior to v6a kept this array in zigzag order. + */ + UINT16 quantval[DCTSIZE2]; /* quantization step for each coefficient */ + /* This field is used only during compression. It's initialized FALSE when + * the table is created, and set TRUE when it's been output to the file. + * You could suppress output of a table by setting this to TRUE. + * (See jpeg_suppress_tables for an example.) + */ + boolean sent_table; /* TRUE when table has been output */ +} JQUANT_TBL; + + +/* Huffman coding tables. */ + +typedef struct { + /* These two fields directly represent the contents of a JPEG DHT marker */ + UINT8 bits[17]; /* bits[k] = # of symbols with codes of */ + /* length k bits; bits[0] is unused */ + UINT8 huffval[256]; /* The symbols, in order of incr code length */ + /* This field is used only during compression. It's initialized FALSE when + * the table is created, and set TRUE when it's been output to the file. + * You could suppress output of a table by setting this to TRUE. + * (See jpeg_suppress_tables for an example.) + */ + boolean sent_table; /* TRUE when table has been output */ +} JHUFF_TBL; + + +/* Basic info about one component (color channel). */ + +typedef struct { + /* These values are fixed over the whole image. */ + /* For compression, they must be supplied by parameter setup; */ + /* for decompression, they are read from the SOF marker. */ + int component_id; /* identifier for this component (0..255) */ + int component_index; /* its index in SOF or cinfo->comp_info[] */ + int h_samp_factor; /* horizontal sampling factor (1..4) */ + int v_samp_factor; /* vertical sampling factor (1..4) */ + int quant_tbl_no; /* quantization table selector (0..3) */ + /* These values may vary between scans. */ + /* For compression, they must be supplied by parameter setup; */ + /* for decompression, they are read from the SOS marker. */ + /* The decompressor output side may not use these variables. */ + int dc_tbl_no; /* DC entropy table selector (0..3) */ + int ac_tbl_no; /* AC entropy table selector (0..3) */ + + /* Remaining fields should be treated as private by applications. */ + + /* These values are computed during compression or decompression startup: */ + /* Component's size in DCT blocks. + * Any dummy blocks added to complete an MCU are not counted; therefore + * these values do not depend on whether a scan is interleaved or not. + */ + JDIMENSION width_in_blocks; + JDIMENSION height_in_blocks; + /* Size of a DCT block in samples. Always DCTSIZE for compression. + * For decompression this is the size of the output from one DCT block, + * reflecting any scaling we choose to apply during the IDCT step. + * Values from 1 to 16 are supported. + * Note that different components may receive different IDCT scalings. + */ +#if JPEG_LIB_VERSION >= 70 + int DCT_h_scaled_size; + int DCT_v_scaled_size; +#else + int DCT_scaled_size; +#endif + /* The downsampled dimensions are the component's actual, unpadded number + * of samples at the main buffer (preprocessing/compression interface), thus + * downsampled_width = ceil(image_width * Hi/Hmax) + * and similarly for height. For decompression, IDCT scaling is included, so + * downsampled_width = ceil(image_width * Hi/Hmax * DCT_[h_]scaled_size/DCTSIZE) + */ + JDIMENSION downsampled_width; /* actual width in samples */ + JDIMENSION downsampled_height; /* actual height in samples */ + /* This flag is used only for decompression. In cases where some of the + * components will be ignored (eg grayscale output from YCbCr image), + * we can skip most computations for the unused components. + */ + boolean component_needed; /* do we need the value of this component? */ + + /* These values are computed before starting a scan of the component. */ + /* The decompressor output side may not use these variables. */ + int MCU_width; /* number of blocks per MCU, horizontally */ + int MCU_height; /* number of blocks per MCU, vertically */ + int MCU_blocks; /* MCU_width * MCU_height */ + int MCU_sample_width; /* MCU width in samples, MCU_width*DCT_[h_]scaled_size */ + int last_col_width; /* # of non-dummy blocks across in last MCU */ + int last_row_height; /* # of non-dummy blocks down in last MCU */ + + /* Saved quantization table for component; NULL if none yet saved. + * See jdinput.c comments about the need for this information. + * This field is currently used only for decompression. + */ + JQUANT_TBL *quant_table; + + /* Private per-component storage for DCT or IDCT subsystem. */ + void *dct_table; +} jpeg_component_info; + + +/* The script for encoding a multiple-scan file is an array of these: */ + +typedef struct { + int comps_in_scan; /* number of components encoded in this scan */ + int component_index[MAX_COMPS_IN_SCAN]; /* their SOF/comp_info[] indexes */ + int Ss, Se; /* progressive JPEG spectral selection parms */ + int Ah, Al; /* progressive JPEG successive approx. parms */ +} jpeg_scan_info; + +/* The decompressor can save APPn and COM markers in a list of these: */ + +typedef struct jpeg_marker_struct *jpeg_saved_marker_ptr; + +struct jpeg_marker_struct { + jpeg_saved_marker_ptr next; /* next in list, or NULL */ + UINT8 marker; /* marker code: JPEG_COM, or JPEG_APP0+n */ + unsigned int original_length; /* # bytes of data in the file */ + unsigned int data_length; /* # bytes of data saved at data[] */ + JOCTET *data; /* the data contained in the marker */ + /* the marker length word is not counted in data_length or original_length */ +}; + +/* Known color spaces. */ + +#define JCS_EXTENSIONS 1 +#define JCS_ALPHA_EXTENSIONS 1 + +typedef enum { + JCS_UNKNOWN, /* error/unspecified */ + JCS_GRAYSCALE, /* monochrome */ + JCS_RGB, /* red/green/blue as specified by the RGB_RED, + RGB_GREEN, RGB_BLUE, and RGB_PIXELSIZE macros */ + JCS_YCbCr, /* Y/Cb/Cr (also known as YUV) */ + JCS_CMYK, /* C/M/Y/K */ + JCS_YCCK, /* Y/Cb/Cr/K */ + JCS_EXT_RGB, /* red/green/blue */ + JCS_EXT_RGBX, /* red/green/blue/x */ + JCS_EXT_BGR, /* blue/green/red */ + JCS_EXT_BGRX, /* blue/green/red/x */ + JCS_EXT_XBGR, /* x/blue/green/red */ + JCS_EXT_XRGB, /* x/red/green/blue */ + /* When out_color_space it set to JCS_EXT_RGBX, JCS_EXT_BGRX, JCS_EXT_XBGR, + or JCS_EXT_XRGB during decompression, the X byte is undefined, and in + order to ensure the best performance, libjpeg-turbo can set that byte to + whatever value it wishes. Use the following colorspace constants to + ensure that the X byte is set to 0xFF, so that it can be interpreted as an + opaque alpha channel. */ + JCS_EXT_RGBA, /* red/green/blue/alpha */ + JCS_EXT_BGRA, /* blue/green/red/alpha */ + JCS_EXT_ABGR, /* alpha/blue/green/red */ + JCS_EXT_ARGB, /* alpha/red/green/blue */ + JCS_RGB565 /* 5-bit red/6-bit green/5-bit blue */ +} J_COLOR_SPACE; + +/* DCT/IDCT algorithm options. */ + +typedef enum { + JDCT_ISLOW, /* accurate integer method */ + JDCT_IFAST, /* less accurate integer method [legacy feature] */ + JDCT_FLOAT /* floating-point method [legacy feature] */ +} J_DCT_METHOD; + +#ifndef JDCT_DEFAULT /* may be overridden in jconfig.h */ +#define JDCT_DEFAULT JDCT_ISLOW +#endif +#ifndef JDCT_FASTEST /* may be overridden in jconfig.h */ +#define JDCT_FASTEST JDCT_IFAST +#endif + +/* Dithering options for decompression. */ + +typedef enum { + JDITHER_NONE, /* no dithering */ + JDITHER_ORDERED, /* simple ordered dither */ + JDITHER_FS /* Floyd-Steinberg error diffusion dither */ +} J_DITHER_MODE; + + +/* Common fields between JPEG compression and decompression master structs. */ + +#define jpeg_common_fields \ + struct jpeg_error_mgr *err; /* Error handler module */ \ + struct jpeg_memory_mgr *mem; /* Memory manager module */ \ + struct jpeg_progress_mgr *progress; /* Progress monitor, or NULL if none */ \ + void *client_data; /* Available for use by application */ \ + boolean is_decompressor; /* So common code can tell which is which */ \ + int global_state /* For checking call sequence validity */ + +/* Routines that are to be used by both halves of the library are declared + * to receive a pointer to this structure. There are no actual instances of + * jpeg_common_struct, only of jpeg_compress_struct and jpeg_decompress_struct. + */ +struct jpeg_common_struct { + jpeg_common_fields; /* Fields common to both master struct types */ + /* Additional fields follow in an actual jpeg_compress_struct or + * jpeg_decompress_struct. All three structs must agree on these + * initial fields! (This would be a lot cleaner in C++.) + */ +}; + +typedef struct jpeg_common_struct *j_common_ptr; +typedef struct jpeg_compress_struct *j_compress_ptr; +typedef struct jpeg_decompress_struct *j_decompress_ptr; + + +/* Master record for a compression instance */ + +struct jpeg_compress_struct { + jpeg_common_fields; /* Fields shared with jpeg_decompress_struct */ + + /* Destination for compressed data */ + struct jpeg_destination_mgr *dest; + + /* Description of source image --- these fields must be filled in by + * outer application before starting compression. in_color_space must + * be correct before you can even call jpeg_set_defaults(). + */ + + JDIMENSION image_width; /* input image width */ + JDIMENSION image_height; /* input image height */ + int input_components; /* # of color components in input image */ + J_COLOR_SPACE in_color_space; /* colorspace of input image */ + + double input_gamma; /* image gamma of input image */ + + /* Compression parameters --- these fields must be set before calling + * jpeg_start_compress(). We recommend calling jpeg_set_defaults() to + * initialize everything to reasonable defaults, then changing anything + * the application specifically wants to change. That way you won't get + * burnt when new parameters are added. Also note that there are several + * helper routines to simplify changing parameters. + */ + +#if JPEG_LIB_VERSION >= 70 + unsigned int scale_num, scale_denom; /* fraction by which to scale image */ + + JDIMENSION jpeg_width; /* scaled JPEG image width */ + JDIMENSION jpeg_height; /* scaled JPEG image height */ + /* Dimensions of actual JPEG image that will be written to file, + * derived from input dimensions by scaling factors above. + * These fields are computed by jpeg_start_compress(). + * You can also use jpeg_calc_jpeg_dimensions() to determine these values + * in advance of calling jpeg_start_compress(). + */ +#endif + + int data_precision; /* bits of precision in image data */ + + int num_components; /* # of color components in JPEG image */ + J_COLOR_SPACE jpeg_color_space; /* colorspace of JPEG image */ + + jpeg_component_info *comp_info; + /* comp_info[i] describes component that appears i'th in SOF */ + + JQUANT_TBL *quant_tbl_ptrs[NUM_QUANT_TBLS]; +#if JPEG_LIB_VERSION >= 70 + int q_scale_factor[NUM_QUANT_TBLS]; +#endif + /* ptrs to coefficient quantization tables, or NULL if not defined, + * and corresponding scale factors (percentage, initialized 100). + */ + + JHUFF_TBL *dc_huff_tbl_ptrs[NUM_HUFF_TBLS]; + JHUFF_TBL *ac_huff_tbl_ptrs[NUM_HUFF_TBLS]; + /* ptrs to Huffman coding tables, or NULL if not defined */ + + UINT8 arith_dc_L[NUM_ARITH_TBLS]; /* L values for DC arith-coding tables */ + UINT8 arith_dc_U[NUM_ARITH_TBLS]; /* U values for DC arith-coding tables */ + UINT8 arith_ac_K[NUM_ARITH_TBLS]; /* Kx values for AC arith-coding tables */ + + int num_scans; /* # of entries in scan_info array */ + const jpeg_scan_info *scan_info; /* script for multi-scan file, or NULL */ + /* The default value of scan_info is NULL, which causes a single-scan + * sequential JPEG file to be emitted. To create a multi-scan file, + * set num_scans and scan_info to point to an array of scan definitions. + */ + + boolean raw_data_in; /* TRUE=caller supplies downsampled data */ + boolean arith_code; /* TRUE=arithmetic coding, FALSE=Huffman */ + boolean optimize_coding; /* TRUE=optimize entropy encoding parms */ + boolean CCIR601_sampling; /* TRUE=first samples are cosited */ +#if JPEG_LIB_VERSION >= 70 + boolean do_fancy_downsampling; /* TRUE=apply fancy downsampling */ +#endif + int smoothing_factor; /* 1..100, or 0 for no input smoothing */ + J_DCT_METHOD dct_method; /* DCT algorithm selector */ + + /* The restart interval can be specified in absolute MCUs by setting + * restart_interval, or in MCU rows by setting restart_in_rows + * (in which case the correct restart_interval will be figured + * for each scan). + */ + unsigned int restart_interval; /* MCUs per restart, or 0 for no restart */ + int restart_in_rows; /* if > 0, MCU rows per restart interval */ + + /* Parameters controlling emission of special markers. */ + + boolean write_JFIF_header; /* should a JFIF marker be written? */ + UINT8 JFIF_major_version; /* What to write for the JFIF version number */ + UINT8 JFIF_minor_version; + /* These three values are not used by the JPEG code, merely copied */ + /* into the JFIF APP0 marker. density_unit can be 0 for unknown, */ + /* 1 for dots/inch, or 2 for dots/cm. Note that the pixel aspect */ + /* ratio is defined by X_density/Y_density even when density_unit=0. */ + UINT8 density_unit; /* JFIF code for pixel size units */ + UINT16 X_density; /* Horizontal pixel density */ + UINT16 Y_density; /* Vertical pixel density */ + boolean write_Adobe_marker; /* should an Adobe marker be written? */ + + /* State variable: index of next scanline to be written to + * jpeg_write_scanlines(). Application may use this to control its + * processing loop, e.g., "while (next_scanline < image_height)". + */ + + JDIMENSION next_scanline; /* 0 .. image_height-1 */ + + /* Remaining fields are known throughout compressor, but generally + * should not be touched by a surrounding application. + */ + + /* + * These fields are computed during compression startup + */ + boolean progressive_mode; /* TRUE if scan script uses progressive mode */ + int max_h_samp_factor; /* largest h_samp_factor */ + int max_v_samp_factor; /* largest v_samp_factor */ + +#if JPEG_LIB_VERSION >= 70 + int min_DCT_h_scaled_size; /* smallest DCT_h_scaled_size of any component */ + int min_DCT_v_scaled_size; /* smallest DCT_v_scaled_size of any component */ +#endif + + JDIMENSION total_iMCU_rows; /* # of iMCU rows to be input to coef ctlr */ + /* The coefficient controller receives data in units of MCU rows as defined + * for fully interleaved scans (whether the JPEG file is interleaved or not). + * There are v_samp_factor * DCTSIZE sample rows of each component in an + * "iMCU" (interleaved MCU) row. + */ + + /* + * These fields are valid during any one scan. + * They describe the components and MCUs actually appearing in the scan. + */ + int comps_in_scan; /* # of JPEG components in this scan */ + jpeg_component_info *cur_comp_info[MAX_COMPS_IN_SCAN]; + /* *cur_comp_info[i] describes component that appears i'th in SOS */ + + JDIMENSION MCUs_per_row; /* # of MCUs across the image */ + JDIMENSION MCU_rows_in_scan; /* # of MCU rows in the image */ + + int blocks_in_MCU; /* # of DCT blocks per MCU */ + int MCU_membership[C_MAX_BLOCKS_IN_MCU]; + /* MCU_membership[i] is index in cur_comp_info of component owning */ + /* i'th block in an MCU */ + + int Ss, Se, Ah, Al; /* progressive JPEG parameters for scan */ + +#if JPEG_LIB_VERSION >= 80 + int block_size; /* the basic DCT block size: 1..16 */ + const int *natural_order; /* natural-order position array */ + int lim_Se; /* min( Se, DCTSIZE2-1 ) */ +#endif + + /* + * Links to compression subobjects (methods and private variables of modules) + */ + struct jpeg_comp_master *master; + struct jpeg_c_main_controller *main; + struct jpeg_c_prep_controller *prep; + struct jpeg_c_coef_controller *coef; + struct jpeg_marker_writer *marker; + struct jpeg_color_converter *cconvert; + struct jpeg_downsampler *downsample; + struct jpeg_forward_dct *fdct; + struct jpeg_entropy_encoder *entropy; + jpeg_scan_info *script_space; /* workspace for jpeg_simple_progression */ + int script_space_size; +}; + + +/* Master record for a decompression instance */ + +struct jpeg_decompress_struct { + jpeg_common_fields; /* Fields shared with jpeg_compress_struct */ + + /* Source of compressed data */ + struct jpeg_source_mgr *src; + + /* Basic description of image --- filled in by jpeg_read_header(). */ + /* Application may inspect these values to decide how to process image. */ + + JDIMENSION image_width; /* nominal image width (from SOF marker) */ + JDIMENSION image_height; /* nominal image height */ + int num_components; /* # of color components in JPEG image */ + J_COLOR_SPACE jpeg_color_space; /* colorspace of JPEG image */ + + /* Decompression processing parameters --- these fields must be set before + * calling jpeg_start_decompress(). Note that jpeg_read_header() initializes + * them to default values. + */ + + J_COLOR_SPACE out_color_space; /* colorspace for output */ + + unsigned int scale_num, scale_denom; /* fraction by which to scale image */ + + double output_gamma; /* image gamma wanted in output */ + + boolean buffered_image; /* TRUE=multiple output passes */ + boolean raw_data_out; /* TRUE=downsampled data wanted */ + + J_DCT_METHOD dct_method; /* IDCT algorithm selector */ + boolean do_fancy_upsampling; /* TRUE=apply fancy upsampling */ + boolean do_block_smoothing; /* TRUE=apply interblock smoothing */ + + boolean quantize_colors; /* TRUE=colormapped output wanted */ + /* the following are ignored if not quantize_colors: */ + J_DITHER_MODE dither_mode; /* type of color dithering to use */ + boolean two_pass_quantize; /* TRUE=use two-pass color quantization */ + int desired_number_of_colors; /* max # colors to use in created colormap */ + /* these are significant only in buffered-image mode: */ + boolean enable_1pass_quant; /* enable future use of 1-pass quantizer */ + boolean enable_external_quant;/* enable future use of external colormap */ + boolean enable_2pass_quant; /* enable future use of 2-pass quantizer */ + + /* Description of actual output image that will be returned to application. + * These fields are computed by jpeg_start_decompress(). + * You can also use jpeg_calc_output_dimensions() to determine these values + * in advance of calling jpeg_start_decompress(). + */ + + JDIMENSION output_width; /* scaled image width */ + JDIMENSION output_height; /* scaled image height */ + int out_color_components; /* # of color components in out_color_space */ + int output_components; /* # of color components returned */ + /* output_components is 1 (a colormap index) when quantizing colors; + * otherwise it equals out_color_components. + */ + int rec_outbuf_height; /* min recommended height of scanline buffer */ + /* If the buffer passed to jpeg_read_scanlines() is less than this many rows + * high, space and time will be wasted due to unnecessary data copying. + * Usually rec_outbuf_height will be 1 or 2, at most 4. + */ + + /* When quantizing colors, the output colormap is described by these fields. + * The application can supply a colormap by setting colormap non-NULL before + * calling jpeg_start_decompress; otherwise a colormap is created during + * jpeg_start_decompress or jpeg_start_output. + * The map has out_color_components rows and actual_number_of_colors columns. + */ + int actual_number_of_colors; /* number of entries in use */ + JSAMPARRAY colormap; /* The color map as a 2-D pixel array */ + + /* State variables: these variables indicate the progress of decompression. + * The application may examine these but must not modify them. + */ + + /* Row index of next scanline to be read from jpeg_read_scanlines(). + * Application may use this to control its processing loop, e.g., + * "while (output_scanline < output_height)". + */ + JDIMENSION output_scanline; /* 0 .. output_height-1 */ + + /* Current input scan number and number of iMCU rows completed in scan. + * These indicate the progress of the decompressor input side. + */ + int input_scan_number; /* Number of SOS markers seen so far */ + JDIMENSION input_iMCU_row; /* Number of iMCU rows completed */ + + /* The "output scan number" is the notional scan being displayed by the + * output side. The decompressor will not allow output scan/row number + * to get ahead of input scan/row, but it can fall arbitrarily far behind. + */ + int output_scan_number; /* Nominal scan number being displayed */ + JDIMENSION output_iMCU_row; /* Number of iMCU rows read */ + + /* Current progression status. coef_bits[c][i] indicates the precision + * with which component c's DCT coefficient i (in zigzag order) is known. + * It is -1 when no data has yet been received, otherwise it is the point + * transform (shift) value for the most recent scan of the coefficient + * (thus, 0 at completion of the progression). + * This pointer is NULL when reading a non-progressive file. + */ + int (*coef_bits)[DCTSIZE2]; /* -1 or current Al value for each coef */ + + /* Internal JPEG parameters --- the application usually need not look at + * these fields. Note that the decompressor output side may not use + * any parameters that can change between scans. + */ + + /* Quantization and Huffman tables are carried forward across input + * datastreams when processing abbreviated JPEG datastreams. + */ + + JQUANT_TBL *quant_tbl_ptrs[NUM_QUANT_TBLS]; + /* ptrs to coefficient quantization tables, or NULL if not defined */ + + JHUFF_TBL *dc_huff_tbl_ptrs[NUM_HUFF_TBLS]; + JHUFF_TBL *ac_huff_tbl_ptrs[NUM_HUFF_TBLS]; + /* ptrs to Huffman coding tables, or NULL if not defined */ + + /* These parameters are never carried across datastreams, since they + * are given in SOF/SOS markers or defined to be reset by SOI. + */ + + int data_precision; /* bits of precision in image data */ + + jpeg_component_info *comp_info; + /* comp_info[i] describes component that appears i'th in SOF */ + +#if JPEG_LIB_VERSION >= 80 + boolean is_baseline; /* TRUE if Baseline SOF0 encountered */ +#endif + boolean progressive_mode; /* TRUE if SOFn specifies progressive mode */ + boolean arith_code; /* TRUE=arithmetic coding, FALSE=Huffman */ + + UINT8 arith_dc_L[NUM_ARITH_TBLS]; /* L values for DC arith-coding tables */ + UINT8 arith_dc_U[NUM_ARITH_TBLS]; /* U values for DC arith-coding tables */ + UINT8 arith_ac_K[NUM_ARITH_TBLS]; /* Kx values for AC arith-coding tables */ + + unsigned int restart_interval; /* MCUs per restart interval, or 0 for no restart */ + + /* These fields record data obtained from optional markers recognized by + * the JPEG library. + */ + boolean saw_JFIF_marker; /* TRUE iff a JFIF APP0 marker was found */ + /* Data copied from JFIF marker; only valid if saw_JFIF_marker is TRUE: */ + UINT8 JFIF_major_version; /* JFIF version number */ + UINT8 JFIF_minor_version; + UINT8 density_unit; /* JFIF code for pixel size units */ + UINT16 X_density; /* Horizontal pixel density */ + UINT16 Y_density; /* Vertical pixel density */ + boolean saw_Adobe_marker; /* TRUE iff an Adobe APP14 marker was found */ + UINT8 Adobe_transform; /* Color transform code from Adobe marker */ + + boolean CCIR601_sampling; /* TRUE=first samples are cosited */ + + /* Aside from the specific data retained from APPn markers known to the + * library, the uninterpreted contents of any or all APPn and COM markers + * can be saved in a list for examination by the application. + */ + jpeg_saved_marker_ptr marker_list; /* Head of list of saved markers */ + + /* Remaining fields are known throughout decompressor, but generally + * should not be touched by a surrounding application. + */ + + /* + * These fields are computed during decompression startup + */ + int max_h_samp_factor; /* largest h_samp_factor */ + int max_v_samp_factor; /* largest v_samp_factor */ + +#if JPEG_LIB_VERSION >= 70 + int min_DCT_h_scaled_size; /* smallest DCT_h_scaled_size of any component */ + int min_DCT_v_scaled_size; /* smallest DCT_v_scaled_size of any component */ +#else + int min_DCT_scaled_size; /* smallest DCT_scaled_size of any component */ +#endif + + JDIMENSION total_iMCU_rows; /* # of iMCU rows in image */ + /* The coefficient controller's input and output progress is measured in + * units of "iMCU" (interleaved MCU) rows. These are the same as MCU rows + * in fully interleaved JPEG scans, but are used whether the scan is + * interleaved or not. We define an iMCU row as v_samp_factor DCT block + * rows of each component. Therefore, the IDCT output contains + * v_samp_factor*DCT_[v_]scaled_size sample rows of a component per iMCU row. + */ + + JSAMPLE *sample_range_limit; /* table for fast range-limiting */ + + /* + * These fields are valid during any one scan. + * They describe the components and MCUs actually appearing in the scan. + * Note that the decompressor output side must not use these fields. + */ + int comps_in_scan; /* # of JPEG components in this scan */ + jpeg_component_info *cur_comp_info[MAX_COMPS_IN_SCAN]; + /* *cur_comp_info[i] describes component that appears i'th in SOS */ + + JDIMENSION MCUs_per_row; /* # of MCUs across the image */ + JDIMENSION MCU_rows_in_scan; /* # of MCU rows in the image */ + + int blocks_in_MCU; /* # of DCT blocks per MCU */ + int MCU_membership[D_MAX_BLOCKS_IN_MCU]; + /* MCU_membership[i] is index in cur_comp_info of component owning */ + /* i'th block in an MCU */ + + int Ss, Se, Ah, Al; /* progressive JPEG parameters for scan */ + +#if JPEG_LIB_VERSION >= 80 + /* These fields are derived from Se of first SOS marker. + */ + int block_size; /* the basic DCT block size: 1..16 */ + const int *natural_order; /* natural-order position array for entropy decode */ + int lim_Se; /* min( Se, DCTSIZE2-1 ) for entropy decode */ +#endif + + /* This field is shared between entropy decoder and marker parser. + * It is either zero or the code of a JPEG marker that has been + * read from the data source, but has not yet been processed. + */ + int unread_marker; + + /* + * Links to decompression subobjects (methods, private variables of modules) + */ + struct jpeg_decomp_master *master; + struct jpeg_d_main_controller *main; + struct jpeg_d_coef_controller *coef; + struct jpeg_d_post_controller *post; + struct jpeg_input_controller *inputctl; + struct jpeg_marker_reader *marker; + struct jpeg_entropy_decoder *entropy; + struct jpeg_inverse_dct *idct; + struct jpeg_upsampler *upsample; + struct jpeg_color_deconverter *cconvert; + struct jpeg_color_quantizer *cquantize; +}; + + +/* "Object" declarations for JPEG modules that may be supplied or called + * directly by the surrounding application. + * As with all objects in the JPEG library, these structs only define the + * publicly visible methods and state variables of a module. Additional + * private fields may exist after the public ones. + */ + + +/* Error handler object */ + +struct jpeg_error_mgr { + /* Error exit handler: does not return to caller */ + void (*error_exit) (j_common_ptr cinfo); + /* Conditionally emit a trace or warning message */ + void (*emit_message) (j_common_ptr cinfo, int msg_level); + /* Routine that actually outputs a trace or error message */ + void (*output_message) (j_common_ptr cinfo); + /* Format a message string for the most recent JPEG error or message */ + void (*format_message) (j_common_ptr cinfo, char *buffer); +#define JMSG_LENGTH_MAX 200 /* recommended size of format_message buffer */ + /* Reset error state variables at start of a new image */ + void (*reset_error_mgr) (j_common_ptr cinfo); + + /* The message ID code and any parameters are saved here. + * A message can have one string parameter or up to 8 int parameters. + */ + int msg_code; +#define JMSG_STR_PARM_MAX 80 + union { + int i[8]; + char s[JMSG_STR_PARM_MAX]; + } msg_parm; + + /* Standard state variables for error facility */ + + int trace_level; /* max msg_level that will be displayed */ + + /* For recoverable corrupt-data errors, we emit a warning message, + * but keep going unless emit_message chooses to abort. emit_message + * should count warnings in num_warnings. The surrounding application + * can check for bad data by seeing if num_warnings is nonzero at the + * end of processing. + */ + long num_warnings; /* number of corrupt-data warnings */ + + /* These fields point to the table(s) of error message strings. + * An application can change the table pointer to switch to a different + * message list (typically, to change the language in which errors are + * reported). Some applications may wish to add additional error codes + * that will be handled by the JPEG library error mechanism; the second + * table pointer is used for this purpose. + * + * First table includes all errors generated by JPEG library itself. + * Error code 0 is reserved for a "no such error string" message. + */ + const char * const *jpeg_message_table; /* Library errors */ + int last_jpeg_message; /* Table contains strings 0..last_jpeg_message */ + /* Second table can be added by application (see cjpeg/djpeg for example). + * It contains strings numbered first_addon_message..last_addon_message. + */ + const char * const *addon_message_table; /* Non-library errors */ + int first_addon_message; /* code for first string in addon table */ + int last_addon_message; /* code for last string in addon table */ +}; + + +/* Progress monitor object */ + +struct jpeg_progress_mgr { + void (*progress_monitor) (j_common_ptr cinfo); + + long pass_counter; /* work units completed in this pass */ + long pass_limit; /* total number of work units in this pass */ + int completed_passes; /* passes completed so far */ + int total_passes; /* total number of passes expected */ +}; + + +/* Data destination object for compression */ + +struct jpeg_destination_mgr { + JOCTET *next_output_byte; /* => next byte to write in buffer */ + size_t free_in_buffer; /* # of byte spaces remaining in buffer */ + + void (*init_destination) (j_compress_ptr cinfo); + boolean (*empty_output_buffer) (j_compress_ptr cinfo); + void (*term_destination) (j_compress_ptr cinfo); +}; + + +/* Data source object for decompression */ + +struct jpeg_source_mgr { + const JOCTET *next_input_byte; /* => next byte to read from buffer */ + size_t bytes_in_buffer; /* # of bytes remaining in buffer */ + + void (*init_source) (j_decompress_ptr cinfo); + boolean (*fill_input_buffer) (j_decompress_ptr cinfo); + void (*skip_input_data) (j_decompress_ptr cinfo, long num_bytes); + boolean (*resync_to_restart) (j_decompress_ptr cinfo, int desired); + void (*term_source) (j_decompress_ptr cinfo); +}; + + +/* Memory manager object. + * Allocates "small" objects (a few K total), "large" objects (tens of K), + * and "really big" objects (virtual arrays with backing store if needed). + * The memory manager does not allow individual objects to be freed; rather, + * each created object is assigned to a pool, and whole pools can be freed + * at once. This is faster and more convenient than remembering exactly what + * to free, especially where malloc()/free() are not too speedy. + * NB: alloc routines never return NULL. They exit to error_exit if not + * successful. + */ + +#define JPOOL_PERMANENT 0 /* lasts until master record is destroyed */ +#define JPOOL_IMAGE 1 /* lasts until done with image/datastream */ +#define JPOOL_NUMPOOLS 2 + +typedef struct jvirt_sarray_control *jvirt_sarray_ptr; +typedef struct jvirt_barray_control *jvirt_barray_ptr; + + +struct jpeg_memory_mgr { + /* Method pointers */ + void *(*alloc_small) (j_common_ptr cinfo, int pool_id, size_t sizeofobject); + void *(*alloc_large) (j_common_ptr cinfo, int pool_id, + size_t sizeofobject); + JSAMPARRAY (*alloc_sarray) (j_common_ptr cinfo, int pool_id, + JDIMENSION samplesperrow, JDIMENSION numrows); + JBLOCKARRAY (*alloc_barray) (j_common_ptr cinfo, int pool_id, + JDIMENSION blocksperrow, JDIMENSION numrows); + jvirt_sarray_ptr (*request_virt_sarray) (j_common_ptr cinfo, int pool_id, + boolean pre_zero, + JDIMENSION samplesperrow, + JDIMENSION numrows, + JDIMENSION maxaccess); + jvirt_barray_ptr (*request_virt_barray) (j_common_ptr cinfo, int pool_id, + boolean pre_zero, + JDIMENSION blocksperrow, + JDIMENSION numrows, + JDIMENSION maxaccess); + void (*realize_virt_arrays) (j_common_ptr cinfo); + JSAMPARRAY (*access_virt_sarray) (j_common_ptr cinfo, jvirt_sarray_ptr ptr, + JDIMENSION start_row, JDIMENSION num_rows, + boolean writable); + JBLOCKARRAY (*access_virt_barray) (j_common_ptr cinfo, jvirt_barray_ptr ptr, + JDIMENSION start_row, JDIMENSION num_rows, + boolean writable); + void (*free_pool) (j_common_ptr cinfo, int pool_id); + void (*self_destruct) (j_common_ptr cinfo); + + /* Limit on memory allocation for this JPEG object. (Note that this is + * merely advisory, not a guaranteed maximum; it only affects the space + * used for virtual-array buffers.) May be changed by outer application + * after creating the JPEG object. + */ + long max_memory_to_use; + + /* Maximum allocation request accepted by alloc_large. */ + long max_alloc_chunk; +}; + + +/* Routine signature for application-supplied marker processing methods. + * Need not pass marker code since it is stored in cinfo->unread_marker. + */ +typedef boolean (*jpeg_marker_parser_method) (j_decompress_ptr cinfo); + + +/* Originally, this macro was used as a way of defining function prototypes + * for both modern compilers as well as older compilers that did not support + * prototype parameters. libjpeg-turbo has never supported these older, + * non-ANSI compilers, but the macro is still included because there is some + * software out there that uses it. + */ + +#define JPP(arglist) arglist + + +/* Default error-management setup */ +EXTERN(struct jpeg_error_mgr *) jpeg_std_error(struct jpeg_error_mgr *err); + +/* Initialization of JPEG compression objects. + * jpeg_create_compress() and jpeg_create_decompress() are the exported + * names that applications should call. These expand to calls on + * jpeg_CreateCompress and jpeg_CreateDecompress with additional information + * passed for version mismatch checking. + * NB: you must set up the error-manager BEFORE calling jpeg_create_xxx. + */ +#define jpeg_create_compress(cinfo) \ + jpeg_CreateCompress((cinfo), JPEG_LIB_VERSION, \ + (size_t)sizeof(struct jpeg_compress_struct)) +#define jpeg_create_decompress(cinfo) \ + jpeg_CreateDecompress((cinfo), JPEG_LIB_VERSION, \ + (size_t)sizeof(struct jpeg_decompress_struct)) +EXTERN(void) jpeg_CreateCompress(j_compress_ptr cinfo, int version, + size_t structsize); +EXTERN(void) jpeg_CreateDecompress(j_decompress_ptr cinfo, int version, + size_t structsize); +/* Destruction of JPEG compression objects */ +EXTERN(void) jpeg_destroy_compress(j_compress_ptr cinfo); +EXTERN(void) jpeg_destroy_decompress(j_decompress_ptr cinfo); + +/* Standard data source and destination managers: stdio streams. */ +/* Caller is responsible for opening the file before and closing after. */ +EXTERN(void) jpeg_stdio_dest(j_compress_ptr cinfo, FILE *outfile); +EXTERN(void) jpeg_stdio_src(j_decompress_ptr cinfo, FILE *infile); + +#if JPEG_LIB_VERSION >= 80 || defined(MEM_SRCDST_SUPPORTED) +/* Data source and destination managers: memory buffers. */ +EXTERN(void) jpeg_mem_dest(j_compress_ptr cinfo, unsigned char **outbuffer, + unsigned long *outsize); +EXTERN(void) jpeg_mem_src(j_decompress_ptr cinfo, + const unsigned char *inbuffer, unsigned long insize); +#endif + +/* Default parameter setup for compression */ +EXTERN(void) jpeg_set_defaults(j_compress_ptr cinfo); +/* Compression parameter setup aids */ +EXTERN(void) jpeg_set_colorspace(j_compress_ptr cinfo, + J_COLOR_SPACE colorspace); +EXTERN(void) jpeg_default_colorspace(j_compress_ptr cinfo); +EXTERN(void) jpeg_set_quality(j_compress_ptr cinfo, int quality, + boolean force_baseline); +EXTERN(void) jpeg_set_linear_quality(j_compress_ptr cinfo, int scale_factor, + boolean force_baseline); +#if JPEG_LIB_VERSION >= 70 +EXTERN(void) jpeg_default_qtables(j_compress_ptr cinfo, + boolean force_baseline); +#endif +EXTERN(void) jpeg_add_quant_table(j_compress_ptr cinfo, int which_tbl, + const unsigned int *basic_table, + int scale_factor, boolean force_baseline); +EXTERN(int) jpeg_quality_scaling(int quality); +EXTERN(void) jpeg_simple_progression(j_compress_ptr cinfo); +EXTERN(void) jpeg_suppress_tables(j_compress_ptr cinfo, boolean suppress); +EXTERN(JQUANT_TBL *) jpeg_alloc_quant_table(j_common_ptr cinfo); +EXTERN(JHUFF_TBL *) jpeg_alloc_huff_table(j_common_ptr cinfo); + +/* Main entry points for compression */ +EXTERN(void) jpeg_start_compress(j_compress_ptr cinfo, + boolean write_all_tables); +EXTERN(JDIMENSION) jpeg_write_scanlines(j_compress_ptr cinfo, + JSAMPARRAY scanlines, + JDIMENSION num_lines); +EXTERN(void) jpeg_finish_compress(j_compress_ptr cinfo); + +#if JPEG_LIB_VERSION >= 70 +/* Precalculate JPEG dimensions for current compression parameters. */ +EXTERN(void) jpeg_calc_jpeg_dimensions(j_compress_ptr cinfo); +#endif + +/* Replaces jpeg_write_scanlines when writing raw downsampled data. */ +EXTERN(JDIMENSION) jpeg_write_raw_data(j_compress_ptr cinfo, JSAMPIMAGE data, + JDIMENSION num_lines); + +/* Write a special marker. See libjpeg.txt concerning safe usage. */ +EXTERN(void) jpeg_write_marker(j_compress_ptr cinfo, int marker, + const JOCTET *dataptr, unsigned int datalen); +/* Same, but piecemeal. */ +EXTERN(void) jpeg_write_m_header(j_compress_ptr cinfo, int marker, + unsigned int datalen); +EXTERN(void) jpeg_write_m_byte(j_compress_ptr cinfo, int val); + +/* Alternate compression function: just write an abbreviated table file */ +EXTERN(void) jpeg_write_tables(j_compress_ptr cinfo); + +/* Write ICC profile. See libjpeg.txt for usage information. */ +EXTERN(void) jpeg_write_icc_profile(j_compress_ptr cinfo, + const JOCTET *icc_data_ptr, + unsigned int icc_data_len); + + +/* Decompression startup: read start of JPEG datastream to see what's there */ +EXTERN(int) jpeg_read_header(j_decompress_ptr cinfo, boolean require_image); +/* Return value is one of: */ +#define JPEG_SUSPENDED 0 /* Suspended due to lack of input data */ +#define JPEG_HEADER_OK 1 /* Found valid image datastream */ +#define JPEG_HEADER_TABLES_ONLY 2 /* Found valid table-specs-only datastream */ +/* If you pass require_image = TRUE (normal case), you need not check for + * a TABLES_ONLY return code; an abbreviated file will cause an error exit. + * JPEG_SUSPENDED is only possible if you use a data source module that can + * give a suspension return (the stdio source module doesn't). + */ + +/* Main entry points for decompression */ +EXTERN(boolean) jpeg_start_decompress(j_decompress_ptr cinfo); +EXTERN(JDIMENSION) jpeg_read_scanlines(j_decompress_ptr cinfo, + JSAMPARRAY scanlines, + JDIMENSION max_lines); +EXTERN(JDIMENSION) jpeg_skip_scanlines(j_decompress_ptr cinfo, + JDIMENSION num_lines); +EXTERN(void) jpeg_crop_scanline(j_decompress_ptr cinfo, JDIMENSION *xoffset, + JDIMENSION *width); +EXTERN(boolean) jpeg_finish_decompress(j_decompress_ptr cinfo); + +/* Replaces jpeg_read_scanlines when reading raw downsampled data. */ +EXTERN(JDIMENSION) jpeg_read_raw_data(j_decompress_ptr cinfo, JSAMPIMAGE data, + JDIMENSION max_lines); + +/* Additional entry points for buffered-image mode. */ +EXTERN(boolean) jpeg_has_multiple_scans(j_decompress_ptr cinfo); +EXTERN(boolean) jpeg_start_output(j_decompress_ptr cinfo, int scan_number); +EXTERN(boolean) jpeg_finish_output(j_decompress_ptr cinfo); +EXTERN(boolean) jpeg_input_complete(j_decompress_ptr cinfo); +EXTERN(void) jpeg_new_colormap(j_decompress_ptr cinfo); +EXTERN(int) jpeg_consume_input(j_decompress_ptr cinfo); +/* Return value is one of: */ +/* #define JPEG_SUSPENDED 0 Suspended due to lack of input data */ +#define JPEG_REACHED_SOS 1 /* Reached start of new scan */ +#define JPEG_REACHED_EOI 2 /* Reached end of image */ +#define JPEG_ROW_COMPLETED 3 /* Completed one iMCU row */ +#define JPEG_SCAN_COMPLETED 4 /* Completed last iMCU row of a scan */ + +/* Precalculate output dimensions for current decompression parameters. */ +#if JPEG_LIB_VERSION >= 80 +EXTERN(void) jpeg_core_output_dimensions(j_decompress_ptr cinfo); +#endif +EXTERN(void) jpeg_calc_output_dimensions(j_decompress_ptr cinfo); + +/* Control saving of COM and APPn markers into marker_list. */ +EXTERN(void) jpeg_save_markers(j_decompress_ptr cinfo, int marker_code, + unsigned int length_limit); + +/* Install a special processing method for COM or APPn markers. */ +EXTERN(void) jpeg_set_marker_processor(j_decompress_ptr cinfo, + int marker_code, + jpeg_marker_parser_method routine); + +/* Read or write raw DCT coefficients --- useful for lossless transcoding. */ +EXTERN(jvirt_barray_ptr *) jpeg_read_coefficients(j_decompress_ptr cinfo); +EXTERN(void) jpeg_write_coefficients(j_compress_ptr cinfo, + jvirt_barray_ptr *coef_arrays); +EXTERN(void) jpeg_copy_critical_parameters(j_decompress_ptr srcinfo, + j_compress_ptr dstinfo); + +/* If you choose to abort compression or decompression before completing + * jpeg_finish_(de)compress, then you need to clean up to release memory, + * temporary files, etc. You can just call jpeg_destroy_(de)compress + * if you're done with the JPEG object, but if you want to clean it up and + * reuse it, call this: + */ +EXTERN(void) jpeg_abort_compress(j_compress_ptr cinfo); +EXTERN(void) jpeg_abort_decompress(j_decompress_ptr cinfo); + +/* Generic versions of jpeg_abort and jpeg_destroy that work on either + * flavor of JPEG object. These may be more convenient in some places. + */ +EXTERN(void) jpeg_abort(j_common_ptr cinfo); +EXTERN(void) jpeg_destroy(j_common_ptr cinfo); + +/* Default restart-marker-resync procedure for use by data source modules */ +EXTERN(boolean) jpeg_resync_to_restart(j_decompress_ptr cinfo, int desired); + +/* Read ICC profile. See libjpeg.txt for usage information. */ +EXTERN(boolean) jpeg_read_icc_profile(j_decompress_ptr cinfo, + JOCTET **icc_data_ptr, + unsigned int *icc_data_len); + + +/* These marker codes are exported since applications and data source modules + * are likely to want to use them. + */ + +#define JPEG_RST0 0xD0 /* RST0 marker code */ +#define JPEG_EOI 0xD9 /* EOI marker code */ +#define JPEG_APP0 0xE0 /* APP0 marker code */ +#define JPEG_COM 0xFE /* COM marker code */ + + +/* If we have a brain-damaged compiler that emits warnings (or worse, errors) + * for structure definitions that are never filled in, keep it quiet by + * supplying dummy definitions for the various substructures. + */ + +#ifdef INCOMPLETE_TYPES_BROKEN +#ifndef JPEG_INTERNALS /* will be defined in jpegint.h */ +struct jvirt_sarray_control { long dummy; }; +struct jvirt_barray_control { long dummy; }; +struct jpeg_comp_master { long dummy; }; +struct jpeg_c_main_controller { long dummy; }; +struct jpeg_c_prep_controller { long dummy; }; +struct jpeg_c_coef_controller { long dummy; }; +struct jpeg_marker_writer { long dummy; }; +struct jpeg_color_converter { long dummy; }; +struct jpeg_downsampler { long dummy; }; +struct jpeg_forward_dct { long dummy; }; +struct jpeg_entropy_encoder { long dummy; }; +struct jpeg_decomp_master { long dummy; }; +struct jpeg_d_main_controller { long dummy; }; +struct jpeg_d_coef_controller { long dummy; }; +struct jpeg_d_post_controller { long dummy; }; +struct jpeg_input_controller { long dummy; }; +struct jpeg_marker_reader { long dummy; }; +struct jpeg_entropy_decoder { long dummy; }; +struct jpeg_inverse_dct { long dummy; }; +struct jpeg_upsampler { long dummy; }; +struct jpeg_color_deconverter { long dummy; }; +struct jpeg_color_quantizer { long dummy; }; +#endif /* JPEG_INTERNALS */ +#endif /* INCOMPLETE_TYPES_BROKEN */ + + +/* + * The JPEG library modules define JPEG_INTERNALS before including this file. + * The internal structure declarations are read only when that is true. + * Applications using the library should not include jpegint.h, but may wish + * to include jerror.h. + */ + +#ifdef JPEG_INTERNALS +#include "jpegint.h" /* fetch private declarations */ +#include "jerror.h" /* fetch error codes too */ +#endif + +#ifdef __cplusplus +#ifndef DONT_USE_EXTERN_C +} +#endif +#endif + +#endif /* JPEGLIB_H */ diff --git a/python-bindings/deps/include/jversion.h b/python-bindings/deps/include/jversion.h new file mode 100644 index 00000000..2ab534af --- /dev/null +++ b/python-bindings/deps/include/jversion.h @@ -0,0 +1,54 @@ +/* + * jversion.h + * + * This file was part of the Independent JPEG Group's software: + * Copyright (C) 1991-2020, Thomas G. Lane, Guido Vollbeding. + * libjpeg-turbo Modifications: + * Copyright (C) 2010, 2012-2021, D. R. Commander. + * For conditions of distribution and use, see the accompanying README.ijg + * file. + * + * This file contains software version identification. + */ + + +#if JPEG_LIB_VERSION >= 80 + +#define JVERSION "8d 15-Jan-2012" + +#elif JPEG_LIB_VERSION >= 70 + +#define JVERSION "7 27-Jun-2009" + +#else + +#define JVERSION "6b 27-Mar-1998" + +#endif + +/* + * NOTE: It is our convention to place the authors in the following order: + * - libjpeg-turbo authors (2009-) in descending order of the date of their + * most recent contribution to the project, then in ascending order of the + * date of their first contribution to the project, then in alphabetical + * order + * - Upstream authors in descending order of the date of the first inclusion of + * their code + */ + +#define JCOPYRIGHT \ + "Copyright (C) 2009-2021 D. R. Commander\n" \ + "Copyright (C) 2015, 2020 Google, Inc.\n" \ + "Copyright (C) 2019-2020 Arm Limited\n" \ + "Copyright (C) 2015-2016, 2018 Matthieu Darbois\n" \ + "Copyright (C) 2011-2016 Siarhei Siamashka\n" \ + "Copyright (C) 2015 Intel Corporation\n" \ + "Copyright (C) 2013-2014 Linaro Limited\n" \ + "Copyright (C) 2013-2014 MIPS Technologies, Inc.\n" \ + "Copyright (C) 2009, 2012 Pierre Ossman for Cendio AB\n" \ + "Copyright (C) 2009-2011 Nokia Corporation and/or its subsidiary(-ies)\n" \ + "Copyright (C) 1999-2006 MIYASAKA Masaru\n" \ + "Copyright (C) 1991-2020 Thomas G. Lane, Guido Vollbeding" + +#define JCOPYRIGHT_SHORT \ + "Copyright (C) 1991-2021 The libjpeg-turbo Project and many others" diff --git a/python-bindings/deps/include/sched.h b/python-bindings/deps/include/sched.h new file mode 100644 index 00000000..f36a97a6 --- /dev/null +++ b/python-bindings/deps/include/sched.h @@ -0,0 +1,183 @@ +/* + * Module: sched.h + * + * Purpose: + * Provides an implementation of POSIX realtime extensions + * as defined in + * + * POSIX 1003.1b-1993 (POSIX.1b) + * + * -------------------------------------------------------------------------- + * + * Pthreads-win32 - POSIX Threads Library for Win32 + * Copyright(C) 1998 John E. Bossom + * Copyright(C) 1999,2005 Pthreads-win32 contributors + * + * Contact Email: rpj@callisto.canberra.edu.au + * + * The current list of contributors is contained + * in the file CONTRIBUTORS included with the source + * code distribution. The list can also be seen at the + * following World Wide Web location: + * http://sources.redhat.com/pthreads-win32/contributors.html + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library in the file COPYING.LIB; + * if not, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ +#if !defined(_SCHED_H) +#define _SCHED_H + +#undef PTW32_SCHED_LEVEL + +#if defined(_POSIX_SOURCE) +#define PTW32_SCHED_LEVEL 0 +/* Early POSIX */ +#endif + +#if defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 199309 +#undef PTW32_SCHED_LEVEL +#define PTW32_SCHED_LEVEL 1 +/* Include 1b, 1c and 1d */ +#endif + +#if defined(INCLUDE_NP) +#undef PTW32_SCHED_LEVEL +#define PTW32_SCHED_LEVEL 2 +/* Include Non-Portable extensions */ +#endif + +#define PTW32_SCHED_LEVEL_MAX 3 + +#if ( defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200112 ) || !defined(PTW32_SCHED_LEVEL) +#define PTW32_SCHED_LEVEL PTW32_SCHED_LEVEL_MAX +/* Include everything */ +#endif + + +#if defined(__GNUC__) && !defined(__declspec) +# error Please upgrade your GNU compiler to one that supports __declspec. +#endif + +/* + * When building the library, you should define PTW32_BUILD so that + * the variables/functions are exported correctly. When using the library, + * do NOT define PTW32_BUILD, and then the variables/functions will + * be imported correctly. + */ +#if !defined(PTW32_STATIC_LIB) +# if defined(PTW32_BUILD) +# define PTW32_DLLPORT __declspec (dllexport) +# else +# define PTW32_DLLPORT __declspec (dllimport) +# endif +#else +# define PTW32_DLLPORT +#endif + +/* + * This is a duplicate of what is in the autoconf config.h, + * which is only used when building the pthread-win32 libraries. + */ + +#if !defined(PTW32_CONFIG_H) +# if defined(WINCE) +# define NEED_ERRNO +# define NEED_SEM +# endif +# if defined(__MINGW64__) +# define HAVE_STRUCT_TIMESPEC +# define HAVE_MODE_T +# elif defined(_UWIN) || defined(__MINGW32__) +# define HAVE_MODE_T +# endif +#endif + +/* + * + */ + +#if PTW32_SCHED_LEVEL >= PTW32_SCHED_LEVEL_MAX +#if defined(NEED_ERRNO) +#include "need_errno.h" +#else +#include +#endif +#endif /* PTW32_SCHED_LEVEL >= PTW32_SCHED_LEVEL_MAX */ + +#if (defined(__MINGW64__) || defined(__MINGW32__)) || defined(_UWIN) +# if PTW32_SCHED_LEVEL >= PTW32_SCHED_LEVEL_MAX +/* For pid_t */ +# include +/* Required by Unix 98 */ +# include +# else + typedef int pid_t; +# endif +#else + typedef int pid_t; +#endif + +/* Thread scheduling policies */ + +enum { + SCHED_OTHER = 0, + SCHED_FIFO, + SCHED_RR, + SCHED_MIN = SCHED_OTHER, + SCHED_MAX = SCHED_RR +}; + +struct sched_param { + int sched_priority; +}; + +#if defined(__cplusplus) +extern "C" +{ +#endif /* __cplusplus */ + +PTW32_DLLPORT int __cdecl sched_yield (void); + +PTW32_DLLPORT int __cdecl sched_get_priority_min (int policy); + +PTW32_DLLPORT int __cdecl sched_get_priority_max (int policy); + +PTW32_DLLPORT int __cdecl sched_setscheduler (pid_t pid, int policy); + +PTW32_DLLPORT int __cdecl sched_getscheduler (pid_t pid); + +/* + * Note that this macro returns ENOTSUP rather than + * ENOSYS as might be expected. However, returning ENOSYS + * should mean that sched_get_priority_{min,max} are + * not implemented as well as sched_rr_get_interval. + * This is not the case, since we just don't support + * round-robin scheduling. Therefore I have chosen to + * return the same value as sched_setscheduler when + * SCHED_RR is passed to it. + */ +#define sched_rr_get_interval(_pid, _interval) \ + ( errno = ENOTSUP, (int) -1 ) + + +#if defined(__cplusplus) +} /* End of extern "C" */ +#endif /* __cplusplus */ + +#undef PTW32_SCHED_LEVEL +#undef PTW32_SCHED_LEVEL_MAX + +#endif /* !_SCHED_H */ + diff --git a/python-bindings/deps/libs/libjpeg.lib b/python-bindings/deps/libs/libjpeg.lib new file mode 100644 index 00000000..4feda9f6 Binary files /dev/null and b/python-bindings/deps/libs/libjpeg.lib differ diff --git a/python-bindings/deps/libs/pthreadVC2static.lib b/python-bindings/deps/libs/pthreadVC2static.lib new file mode 100644 index 00000000..9d44f368 Binary files /dev/null and b/python-bindings/deps/libs/pthreadVC2static.lib differ diff --git a/python-bindings/deps/libs/zlib.lib b/python-bindings/deps/libs/zlib.lib new file mode 100644 index 00000000..41de7e99 Binary files /dev/null and b/python-bindings/deps/libs/zlib.lib differ diff --git a/python-bindings/example.py b/python-bindings/example.py new file mode 100644 index 00000000..64bb1ef4 --- /dev/null +++ b/python-bindings/example.py @@ -0,0 +1,70 @@ +from artoolkitnft import arcontrollerNFT +import asyncio +import numpy as np +from PIL import Image + +async def main(): + arnft = arcontrollerNFT.ARControllerNFT(1637, 2048, '../examples/Data/camera_para.dat') + nft = await arnft._initialize() + await nft.loadNFTMarkers(['../examples/DataNFT/pinball']) + print(nft) + + obj = nft.trackNFTMarkerId(nft.id) + print('obj is: ', obj) + + # Load the test image + image_path = './pinball-test.png' + + image = Image.open(image_path) + print(f'Original image shape: {image.size}, mode: {image.mode}') + image = image.convert('RGBA') + image_gray = image.convert('L') + gray_data = np.array(image_gray) + nft.setGrayData(gray_data) + # image = image.transpose(Image.Transpose.ROTATE_90) + # image = image.transpose(Image.Transpose.ROTATE_270) + # image = image.transpose(Image.Transpose.FLIP_TOP_BOTTOM) + # image = image.transpose(Image.Transpose.ROTATE_90) + # image = image.resize((640, 480), Image.Resampling.LANCZOS) + # image = image.transpose(Image.Transpose.FLIP_LEFT_RIGHT) + # image.show() + print(f'Input image shape: {image.size}, mode: {image.mode}') + print(f"First pixel values: {image.getpixel((0, 0))}") + rgba = np.ascontiguousarray(np.array(image), np.uint8) + # rgba = np.transpose(rgba, (1, 0, 2)) # Swap height and width dimensions + # rgba = np.transpose(rgba) + # Flip the image vertically + # image = image.transpose(Image.FLIP_TOP_BOTTOM) + + # Create a mock image object with the data attribute + class MockImage: + def __init__(self, array): + self.data = array + self.width = array.shape[1] # Width of the image + self.height = array.shape[0] # Height of the image + self.format = 'RGBA' # Or the format expected by the binding (e.g., 'BGRA') + + mock_image = MockImage(rgba) + + print('mock_image type: ', type(mock_image.data)) + print('mock_image width:', mock_image.data.shape) + + # Add event listener for detecting NFT marker + def on_get_nft_marker(ev): + print("getNFTMarker", ev) + info = ev + print('info:', info) + #assert "id" in info + #assert info["id"] == 0 + #assert info["found"] == 1 + + arnft.add_event_listener("getNFTMarker", on_get_nft_marker) + + # Process the image + print("Processing image...") + for i in range(1): + nft.process(mock_image) + await asyncio.sleep(0) # let event loop dispatch + print("Image processed.") + +asyncio.run(main()) \ No newline at end of file diff --git a/python-bindings/pinball-test.png b/python-bindings/pinball-test.png new file mode 100644 index 00000000..bfb49bb8 Binary files /dev/null and b/python-bindings/pinball-test.png differ diff --git a/python-bindings/pyproject.toml b/python-bindings/pyproject.toml new file mode 100644 index 00000000..935885db --- /dev/null +++ b/python-bindings/pyproject.toml @@ -0,0 +1,22 @@ +[project] +name = "artoolkitnft" +version = "0.0.10" +authors = [ + { name="Walter Perdan", email="github@kalwaltart.it" }, +] +description = "This is a Python binding project for jsartoolkitNFT, which integrates WebARKitLib with Python using pybind11. It allows for augmented reality applications to be developed in Python by providing bindings to the underlying C/C++ WebARKitLib library." +readme = "README.md" +requires-python = ">=3.8" +classifiers = [ + "Programming Language :: Python :: 3", + "Operating System :: OS Independent", + "License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)", +] + +[project.urls] +Homepage = "https://github.com/webarkit/jsartoolkitNFT" +Issues = "https://github.com/webarkit/jsartoolkitNFT/issues" + +[build-system] +requires = ["setuptools>=42", "wheel", "pybind11"] +build-backend = "setuptools.build_meta" \ No newline at end of file diff --git a/python-bindings/setup.py b/python-bindings/setup.py new file mode 100644 index 00000000..5cb445a4 --- /dev/null +++ b/python-bindings/setup.py @@ -0,0 +1,228 @@ +from glob import glob +from setuptools import setup, find_packages +from pybind11.setup_helpers import Pybind11Extension, build_ext +import pybind11 +import os +import shutil +import subprocess +import sys + +LIBJPEG_VERSION = '9c' +LIBJPEG_URL = f'http://www.ijg.org/files/jpegsrc.v{LIBJPEG_VERSION}.tar.gz' +LIBJPEG_DIR = 'deps/libjpeg' +ZLIB_DIR = '../emscripten/zlib' +CONFIG_H_IN = os.path.join(os.path.dirname(__file__), '../emscripten/WebARKitLib/include/AR/config.h.in') +CONFIG_H = os.path.join(os.path.dirname(__file__), '../emscripten/WebARKitLib/include/AR/config.h') + +def download_and_extract(url, dest): + subprocess.run(['curl', '-L', url, '-o', 'libjpeg.tar.gz'], check=True) + subprocess.run(['tar', 'xzf', 'libjpeg.tar.gz'], check=True) + shutil.move(f'jpeg-{LIBJPEG_VERSION}', dest) + os.remove('libjpeg.tar.gz') + +def build_zlib(): + build_dir = os.path.join(ZLIB_DIR, 'build') + install_dir = os.path.join(ZLIB_DIR, 'install') + os.makedirs(build_dir, exist_ok=True) + os.makedirs(install_dir, exist_ok=True) + cmake_command = [ + 'cmake', + '-S', ZLIB_DIR, + '-B', build_dir, + '-DCMAKE_BUILD_TYPE=Release', + f'-DCMAKE_INSTALL_PREFIX={install_dir}' + ] + cmake_command += ["-DCMAKE_POLICY_VERSION_MINIMUM=3.5"] + build_command = ['cmake', '--build', build_dir, '--config', 'Release'] + install_command = ['cmake', '--install', build_dir] + + try: + subprocess.run(cmake_command, check=True) + subprocess.run(build_command, check=True) + subprocess.run(install_command, check=True) + except subprocess.CalledProcessError as e: + print(f"Error building zlib: {e}") + sys.exit(1) + +def build_libjpeg(): + if sys.platform == 'win32': + # Use MSYS2 to build libjpeg on Windows + build_dir = os.path.abspath(os.path.join(LIBJPEG_DIR, 'build')) + os.makedirs(build_dir, exist_ok=True) + # Full path to MSYS2 + msys2_path = r'C:\msys64\usr\bin' # Adjust this path if necessary + os.environ['PATH'] = msys2_path + os.pathsep + os.environ['PATH'] + # Convert Windows path to Unix-style path using MSYS2 + try: + build_dir_unix = subprocess.check_output(['bash', '-c', f'cygpath -u {build_dir}']).strip().decode('utf-8') + print(f"Converted build directory: {build_dir_unix}") + except subprocess.CalledProcessError as e: + print(f"Error converting path: {e}") + sys.exit(1) + # Change the installation directory to a location with write permissions + install_dir = os.path.join(build_dir, 'install') + os.makedirs(install_dir, exist_ok=True) + install_dir_unix = subprocess.check_output(['bash', '-c', f'cygpath -u {install_dir}']).strip().decode('utf-8') + subprocess.run(['bash', '-c', f'./configure --prefix={install_dir_unix}'], cwd=LIBJPEG_DIR, check=True) + subprocess.run(['bash', '-c', 'make'], cwd=LIBJPEG_DIR, check=True) + subprocess.run(['bash', '-c', 'make install'], cwd=LIBJPEG_DIR, check=True) + else: + build_dir = os.path.abspath(os.path.join(LIBJPEG_DIR, 'build')) + os.makedirs(build_dir, exist_ok=True) + subprocess.run(['./configure', '--prefix=' + build_dir], cwd=LIBJPEG_DIR, check=True) + subprocess.run(['make'], cwd=LIBJPEG_DIR, check=True) + subprocess.run(['make', 'install'], cwd=LIBJPEG_DIR, check=True) + +def generate_config_h(): + with open(CONFIG_H_IN, 'r') as file: + config_h_content = file.read() + + config_h_content = config_h_content.replace('#undef ARVIDEO_INPUT_DEFAULT_DUMMY', + '#define ARVIDEO_INPUT_DEFAULT_DUMMY') + + with open(CONFIG_H, 'w') as file: + file.write(config_h_content) + +# Check if the CMakeLists.txt file exists in the zlib directory +if not os.path.exists(os.path.join(ZLIB_DIR, 'CMakeLists.txt')): + print("CMakeLists.txt not found in zlib directory. Running 'git submodule update --init'.") + subprocess.run(['git', 'submodule', 'update', '--init'], check=True) + +# Build zlib +build_zlib() + +# Check if the libjpeg directory exists, if not, download and extract it +if not os.path.exists(LIBJPEG_DIR): + download_and_extract(LIBJPEG_URL, LIBJPEG_DIR) + +# Build libjpeg +if sys.platform.startswith('linux'): + build_libjpeg() + +# Generate config.h from config.h.in +generate_config_h() + +# Windows-specific step to install pthread static library using vcpkg +if sys.platform == 'win32': + print("Running Windows-specific setup step to install pthread static library") + vcpkg_path = os.path.join(os.getcwd(), 'vcpkg') + if not os.path.exists(vcpkg_path): + subprocess.run(['git', 'clone', 'https://github.com/microsoft/vcpkg.git'], check=True) + subprocess.run([os.path.join(vcpkg_path, 'bootstrap-vcpkg.bat')], check=True) + + # Prefer Chocolatey, fall back to winget, otherwise instruct the user + choco = shutil.which('choco') + winget = shutil.which('winget') + + if choco: + subprocess.run(['choco', 'install', 'cmake', 'ninja', 'visualstudio2019buildtools', 'visualstudio2019-workload-vctools', '-y'], check=True) + elif winget: + # Install common tools with winget where possible + subprocess.run(['winget', 'install', '--exact', 'Kitware.CMake', '-e'], check=True) + subprocess.run(['winget', 'install', '--exact', 'Ninja', '-e'], check=True) + print("If Visual Studio Build Tools are required, install them via Visual Studio Installer or winget; continuing.") + else: + print("Neither `choco` nor `winget` found. Please install `cmake` and `ninja` and Visual Studio Build Tools manually and ensure they are on PATH.") + print("Install Chocolatey: https://chocolatey.org/install or use Windows Package Manager (winget).") + sys.exit(1) + + subprocess.run([os.path.join(vcpkg_path, 'vcpkg'), 'install', 'pthreads:x64-windows-static'], check=True) + os.environ['VCPKG_ROOT'] = vcpkg_path + + +# Sort the list of files +sorted_ar_files = sorted(glob('../emscripten/WebARKitLib/lib/SRC/AR/*.c')) +sorted_ar2_files = sorted(glob('../emscripten/WebARKitLib/lib/SRC/AR2/*.c')) +sorted_arutil_files = sorted(glob('../emscripten/WebARKitLib/lib/SRC/ARUtil/*.c')) +sorted_arLabeling_files = sorted(glob('../emscripten/WebARKitLib/lib/SRC/AR/arLabelingSub/*.c')) +sorted_aricp_files = sorted(glob('../emscripten/WebARKitLib/lib/SRC/ARICP/*.c')) + +include_dirs = [ + pybind11.get_include(), + '../emscripten', + '../emscripten/WebARKitLib/include', + '../emscripten/WebARKitLib/lib/SRC/KPM/FreakMatcher', + '../emscripten/zlib' +] + +library_dirs = [] +libraries = [] +extra_compile_args = [] + +if sys.platform == 'win32': + include_dirs.extend([ + 'deps/include', + 'deps/libjpeg', + '../emscripten/zlib', + '../emscripten/zlib/build', + 'vcpkg/packages/pthreads_x64-windows-static/include', + os.path.join(os.getenv('VCPKG_ROOT', 'vcpkg'), 'installed', 'x64-windows', 'include') + ]) + library_dirs.extend([ + 'deps/libs', + '../emscripten/zlib/build/Release', + 'vcpkg/packages/pthreads_x64-windows-static/lib', + os.path.join(os.getenv('VCPKG_ROOT', 'vcpkg'), 'installed', 'x64-windows', 'lib') + ]) + libraries.extend(['zlib', 'libjpeg', 'Advapi32', 'Shell32', 'pthreadVC3', 'pthreadVC2static']) + extra_compile_args.extend(['/std:c++17', '/Dcpu_set_t=struct{unsigned long __bits[1024 / (8 * sizeof(unsigned long))];}']) # Set the C++ standard to C++17 and define cpu_set_t +else: + include_dirs.append(os.path.join(LIBJPEG_DIR, 'build', 'include')) + library_dirs.append(os.path.join(LIBJPEG_DIR, 'build', 'lib')) + libraries.extend(['z', 'm', 'jpeg']) + +ext_modules = [ + Pybind11Extension( + 'artoolkitnft_core', + sources=sorted_ar_files + sorted_ar2_files + sorted_arutil_files + sorted_arLabeling_files + sorted_aricp_files + [ + 'ARToolKitNFT_py.cpp', + '../emscripten/trackingMod.c', + '../emscripten/trackingMod2d.c', + '../emscripten/WebARKitLib/lib/SRC/KPM/kpmFopen.c', + '../emscripten/WebARKitLib/lib/SRC/KPM/kpmHandle.cpp', + '../emscripten/WebARKitLib/lib/SRC/KPM/kpmMatching.cpp', + '../emscripten/WebARKitLib/lib/SRC/KPM/kpmRefDataSet.cpp', + '../emscripten/WebARKitLib/lib/SRC/KPM/kpmResult.cpp', + '../emscripten/WebARKitLib/lib/SRC/KPM/kpmUtil.cpp', + "../emscripten/WebARKitLib/lib/SRC/KPM/FreakMatcher/detectors/DoG_scale_invariant_detector.cpp", + "../emscripten/WebARKitLib/lib/SRC/KPM/FreakMatcher/detectors/gaussian_scale_space_pyramid.cpp", + "../emscripten/WebARKitLib/lib/SRC/KPM/FreakMatcher/detectors/gradients.cpp", + "../emscripten/WebARKitLib/lib/SRC/KPM/FreakMatcher/detectors/orientation_assignment.cpp", + "../emscripten/WebARKitLib/lib/SRC/KPM/FreakMatcher/detectors/pyramid.cpp", + "../emscripten/WebARKitLib/lib/SRC/KPM/FreakMatcher/facade/visual_database_facade.cpp", + "../emscripten/WebARKitLib/lib/SRC/KPM/FreakMatcher/matchers/hough_similarity_voting.cpp", + "../emscripten/WebARKitLib/lib/SRC/KPM/FreakMatcher/matchers/freak.cpp", + "../emscripten/WebARKitLib/lib/SRC/KPM/FreakMatcher/framework/date_time.cpp", + "../emscripten/WebARKitLib/lib/SRC/KPM/FreakMatcher/framework/image.cpp", + "../emscripten/WebARKitLib/lib/SRC/KPM/FreakMatcher/framework/logger.cpp", + "../emscripten/WebARKitLib/lib/SRC/KPM/FreakMatcher/framework/timers.cpp", + ], + include_dirs=include_dirs, + libraries=libraries, + library_dirs=library_dirs, + language='c++', + extra_compile_args=extra_compile_args + ), +] + +setup( + name='artoolkitnft', + version='0.0.10', + author='Walter Perdan', + author_email='github@kalwaltart.it', + description='This is a Python binding project for jsartoolkitNFT, which integrates WebARKitLib with Python using pybind11. It allows for augmented reality applications to be developed in Python by providing bindings to the underlying C/C++ WebARKitLib library.', + long_description=open('README.md').read(), + long_description_content_type='text/markdown', + url='https://github.com/webarkit/jsartoolkitNFT', + classifiers=[ + 'Programming Language :: Python :: 3', + 'Operating System :: OS Independent', + 'License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)', + ], + packages=find_packages(where='src', exclude=['deps', 'vcpkg', 'emscripten']), + package_dir={'': 'src'}, + ext_modules=ext_modules, + cmdclass={'build_ext': build_ext}, + zip_safe=False, + python_requires='>=3.8', +) \ No newline at end of file diff --git a/python-bindings/src/artoolkitnft/__init__.py b/python-bindings/src/artoolkitnft/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/python-bindings/src/artoolkitnft/arcontrollerNFT.py b/python-bindings/src/artoolkitnft/arcontrollerNFT.py new file mode 100644 index 00000000..373beb9d --- /dev/null +++ b/python-bindings/src/artoolkitnft/arcontrollerNFT.py @@ -0,0 +1,324 @@ +import artoolkitnft_core +import time +import asyncio +import numpy as np + +class EventDispatcher: + def __init__(self): + self.listeners = {} + + def add_event_listener(self, event_name, callback): + if event_name not in self.listeners: + self.listeners[event_name] = [] + self.listeners[event_name].append(callback) + + def remove_event_listener(self, event_name, callback): + if event_name in self.listeners: + self.listeners[event_name].remove(callback) + + def dispatch_event(self, event_name, event_data=None): + if event_name in self.listeners: + for callback in self.listeners[event_name]: + callback(event_data) + +class ARControllerNFT(EventDispatcher): + NFT_MARKER = artoolkitnft_core.NFT_MARKER + + def __init__(self, width=None, height=None, cameraParam=None): + super().__init__() + self.id = -1 + self._width = width + self._height = height + self._cameraParam = cameraParam + self.cameraId = -1 + + self.artoolkitNFT = None + + self.nftMarkers = {} # Initialize as a dictionary + + self.transform_mat = [0.0] * 16 + self.transformGL_RH = [0.0] * 16 + self.camera_mat = [0.0] * 16 + + self.videoWidth = width + self.videoHeight = height + self.videoSize = self.videoWidth * self.videoHeight if width and height else 0 + + self.framesize = None + self.videoLuma = None + self.grayscaleEnabled = False + self.grayscaleSource = None + + self.nftMarkerFound = False + self.nftMarkerFoundTime = 0 + self.nftMarkerCount = 0 + self.defaultMarkerWidth = 1 + + self._bwpointer = None + + def process(self, image): + self._copyImageToHeap(image) + + for k in self.nftMarkers: + o = self.nftMarkers[k] + o['inPrevious'] = o['inCurrent'] + o['inCurrent'] = False + + nftMarkerCount = self.nftMarkerCount + self.detectNFTMarker() + + MARKER_LOST_TIME = 200 + + for i in range(nftMarkerCount): + nftMarkerInfo = self.getNFTMarker(i) + markerType = artoolkitnft_core.NFT_MARKER + print('nftMarkerInfo:', nftMarkerInfo) + + if nftMarkerInfo['found']: + self.nftMarkerFound = i + self.nftMarkerFoundTime = time.time() + + visible = self.trackNFTMarkerId(i) + visible['matrix'] = nftMarkerInfo['pose'] + visible['inCurrent'] = True + self.transMatToGLMat(visible['matrix'], self.transform_mat) + self.transformGL_RH = self.arglCameraViewRHf(self.transform_mat) + self.dispatch_event('getNFTMarker', { + 'index': i, + 'type': markerType, + 'marker': nftMarkerInfo, + 'matrix': self.transform_mat, + 'matrixGL_RH': self.transformGL_RH, + }) + elif self.nftMarkerFound == i: + if time.time() - self.nftMarkerFoundTime > MARKER_LOST_TIME: + self.nftMarkerFound = False + self.dispatch_event('lostNFTMarker', { + 'index': i, + 'type': markerType, + 'marker': nftMarkerInfo, + 'matrix': self.transform_mat, + 'matrixGL_RH': self.transformGL_RH, + }) + + def detectNFTMarker(self): + return self.artoolkitNFT.detectNFTMarker() + + def trackNFTMarkerId(self, id, markerWidth=None): + obj = self.nftMarkers.get(id) + if not obj: + self.nftMarkers[id] = obj = { + 'inPrevious': False, + 'inCurrent': False, + 'matrix': [0.0] * 12, + 'matrixGL_RH': [0.0] * 12, + 'markerWidth': markerWidth or self.defaultMarkerWidth, + } + if markerWidth: + obj['markerWidth'] = markerWidth + return obj + + def getNFTMarker(self, markerIndex): + return self.artoolkitNFT.getNFTMarker(markerIndex) + + def getNFTData(self, index): + return self.artoolkitNFT.getNFTData(index) + + def debugSetup(self): + self.setDebugMode(True) + self._bwpointer = self.getProcessingImage() + + def transMatToGLMat(self, transMat, glMat=None, scale=None): + if glMat is None: + glMat = [0.0] * 16 + + glMat[0] = transMat[0] + glMat[1] = transMat[1] + glMat[2] = transMat[2] + glMat[3] = transMat[3] + glMat[4] = transMat[4] + glMat[5] = transMat[5] + glMat[6] = transMat[6] + glMat[7] = transMat[7] + glMat[8] = transMat[8] + glMat[9] = transMat[9] + glMat[10] = transMat[10] + glMat[11] = transMat[11] + glMat[12] = 0.0 + glMat[13] = 0.0 + glMat[14] = 0.0 + glMat[15] = 1.0 + + if scale is not None and scale != 0.0: + glMat[12] *= scale + glMat[13] *= scale + glMat[14] *= scale + + return glMat + + def arglCameraViewRHf(self, glMatrix, glRhMatrix=None, scale=None): + if glRhMatrix is None: + glRhMatrix = [0.0] * 16 + + glRhMatrix[0] = glMatrix[0] + glRhMatrix[4] = glMatrix[4] + glRhMatrix[8] = glMatrix[8] + glRhMatrix[12] = glMatrix[12] + glRhMatrix[1] = -glMatrix[1] + glRhMatrix[5] = -glMatrix[5] + glRhMatrix[9] = -glMatrix[9] + glRhMatrix[13] = -glMatrix[13] + glRhMatrix[2] = -glMatrix[2] + glRhMatrix[6] = -glMatrix[6] + glRhMatrix[10] = -glMatrix[10] + glRhMatrix[14] = -glMatrix[14] + glRhMatrix[3] = 0 + glRhMatrix[7] = 0 + glRhMatrix[11] = 0 + glRhMatrix[15] = 1 + + if scale is not None and scale != 0.0: + glRhMatrix[12] *= scale + glRhMatrix[13] *= scale + glRhMatrix[14] *= scale + + return glRhMatrix + + def getTransformationMatrix(self): + return self.transform_mat + + def getCameraMatrix(self): + return self.camera_mat + + def setDebugMode(self, mode): + return self.artoolkitNFT.setDebugMode(mode) + + def getDebugMode(self): + return self.artoolkitNFT.getDebugMode() + + def getProcessingImage(self): + return self.artoolkitNFT.getProcessingImage() + + def setLogLevel(self, mode): + return self.artoolkitNFT.setLogLevel(mode) + + def getLogLevel(self): + return self.artoolkitNFT.getLogLevel() + + def setProjectionNearPlane(self, value): + return self.artoolkitNFT.setProjectionNearPlane(value) + + def getProjectionNearPlane(self): + return self.artoolkitNFT.getProjectionNearPlane() + + def setProjectionFarPlane(self, value): + return self.artoolkitNFT.setProjectionFarPlane(value) + + def getProjectionFarPlane(self): + return self.artoolkitNFT.getProjectionFarPlane() + + def setThresholdMode(self, mode): + return self.artoolkitNFT.setThresholdMode(mode) + + def getThresholdMode(self): + return self.artoolkitNFT.getThresholdMode() + + def setThreshold(self, threshold): + return self.artoolkitNFT.setThreshold(threshold) + + def getThreshold(self): + return self.artoolkitNFT.getThreshold() + + def setImageProcMode(self, mode): + return self.artoolkitNFT.setImageProcMode(mode) + + def getImageProcMode(self): + return self.artoolkitNFT.getImageProcMode() + + def setGrayData(self, data): + self.grayscaleEnabled = True + self.grayscaleSource = data + + def _rgba_to_grayscale(self, rgba_image): + # Extract the R, G, B channels + r, g, b = rgba_image[:, :, 0], rgba_image[:, :, 1], rgba_image[:, :, 2] + # Apply the luminance formula + grayscale_image = 0.299 * r + 0.587 * g + 0.114 * b + return grayscale_image.astype(np.uint8) + + def _copyImageToHeap(self, sourceImage): + if sourceImage is None: + print("Error: no provided imageData to ARControllerNFT") + return + + if isinstance(sourceImage.data, np.ndarray): + data = sourceImage.data + else: + print("Error: sourceImage.data is not a numpy array") + return + + if self.videoLuma is not None: + if not self.grayscaleEnabled: + self.videoLuma = self._rgba_to_grayscale(data) + else: + if self.grayscaleSource is not None: + self.videoLuma = self.grayscaleSource + else: + print("Error: grayscaleSource is not initialized") + + if self.videoLuma is not None: + # Ensure videoFrame has the correct shape and dimensions + videoFrame = data.flatten() + print("videoFrame:", videoFrame) + videoLuma = self.videoLuma.flatten() + print("videoLuma:", videoLuma) + self.artoolkitNFT.passVideoData(np.array(videoFrame, dtype=np.uint8), np.array(videoLuma, dtype=np.uint8)) + return True + + return False + + async def _initialize(self): + self.artoolkitNFT = artoolkitnft_core.ARToolKitNFT() + + print("[ARControllerNFT]", "ARToolkitNFT initialized") + + self.cameraId = await self.async_load_camera(self._cameraParam) + print("[ARControllerNFT]", "Camera params loaded with ID", self.cameraId) + + self.id = self.artoolkitNFT.setup(self._width, self._height, self.cameraId) + print("[ARControllerNFT]", "Got ID from setup", self.id) + + self._initNFT() + + self.framesize = self._width * self._height + self.videoLuma = [0] * self.framesize + self.camera_mat = self.artoolkitNFT.getCameraLens() + + self.setProjectionNearPlane(0.1) + self.setProjectionFarPlane(1000) + + return self + + def _initNFT(self): + self.artoolkitNFT.setupAR2() + + async def async_load_camera(self, cparam_name): + loop = asyncio.get_event_loop() + camera_id = await loop.run_in_executor(None, self.artoolkitNFT.loadCamera, cparam_name) + return camera_id + + async def loadNFTMarker(self, urlOrData): + loop = asyncio.get_event_loop() + marker_ids = await loop.run_in_executor(None, self.artoolkitNFT.addNFTMarkers, [urlOrData]) + self.nftMarkerCount += len(marker_ids) + return marker_ids + + async def loadNFTMarkers(self, urlOrDataList): + loop = asyncio.get_event_loop() + marker_ids = await loop.run_in_executor(None, self.artoolkitNFT.addNFTMarkers, urlOrDataList) + self.nftMarkerCount += len(marker_ids) + return marker_ids + + @staticmethod + def some_static_method(arg1, arg2): + return arg1 + arg2 \ No newline at end of file diff --git a/python-bindings/test.py b/python-bindings/test.py new file mode 100644 index 00000000..f295d6fd --- /dev/null +++ b/python-bindings/test.py @@ -0,0 +1,33 @@ +import jsartoolkitNFT +import numpy as np + +nft = jsartoolkitNFT.ARToolKitNFT() +cameraId = nft.loadCamera('../examples/Data/camera_para.dat') +width = 640 +height = 480 +id = nft.setup(width, height, cameraId) +nft.setupAR2() +camera_mat = nft.getCameraLens() +print(camera_mat) +framesize = width * height +videoLuma = np.zeros((height, width, 1), dtype=np.uint8) + +nft.setProjectionNearPlane(0.1) +nft.setProjectionFarPlane(1000) + +marker = nft.addNFTMarkers(['../examples/DataNFT/pinball']) + +nftData = nft.getNFTData(id) +print(nftData) +print(f"Width: {nftData.width_NFT}") +print(f"Height: {nftData.height_NFT}") +print(f"DPI: {nftData.dpi_NFT}") + +nft.detectNFTMarker() + +info = nft.getNFTMarkerInfo(id) +print(info) +print("id:", info["id"]) +print("error:", info["error"]) +print("found:", info["found"]) +print("pose:", info["pose"]) \ No newline at end of file diff --git a/python-bindings/test_arcontrollerNFT.py b/python-bindings/test_arcontrollerNFT.py new file mode 100644 index 00000000..7ccd6441 --- /dev/null +++ b/python-bindings/test_arcontrollerNFT.py @@ -0,0 +1,124 @@ +from artoolkitnft import arcontrollerNFT +import numpy as np +import pytest +import numpy.testing as npt +from PIL import Image + +@pytest.mark.asyncio +class TestNFT: + async def asyncSetUp(self): + self.nft = arcontrollerNFT.ARControllerNFT(1920, 1021, '../examples/Data/camera_para.dat') + await self.nft._initialize() + self.nearPlane = 0.1 + self.farPlane = 1000 + + @pytest.mark.asyncio + async def test_load_camera(self): + await self.asyncSetUp() + assert self.nft.cameraId == 0 + + @pytest.mark.asyncio + async def test_setup(self): + await self.asyncSetUp() + assert self.nft.id == 1 + + @pytest.mark.asyncio + async def test_setupAR2(self): + await self.asyncSetUp() + assert self.nft._initNFT() == None + + # @pytest.mark.asyncio + # async def test_get_camera_lens(self): + # await self.asyncSetUp() + # expected_lens = np.array([ + # 1.90724698e+00, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, + # 0.00000000e+00, 2.53244770e+00, 0.00000000e+00, 0.00000000e+00, + # -1.23565148e-02, -7.90590035e-03, -1.00000020e+00, -1.00000000e+00, + # 0.00000000e+00, 0.00000000e+00, -2.00000020e-04, 0.00000000e+00 + # ]) + # npt.assert_allclose(self.nft.artoolkitNFT.getCameraLens(), expected_lens, rtol=1e-5, atol=1e-8) + + @pytest.mark.asyncio + async def test_set_projection_near_plane(self): + await self.asyncSetUp() + self.nft.setProjectionNearPlane(self.nearPlane) + assert self.nft.getProjectionNearPlane() == self.nearPlane + + @pytest.mark.asyncio + async def test_set_projection_far_plane(self): + await self.asyncSetUp() + self.nft.setProjectionFarPlane(self.farPlane) + assert self.nft.getProjectionFarPlane() == self.farPlane + + @pytest.mark.asyncio + async def test_add_nft_markers(self): + await self.asyncSetUp() + marker_ids = await self.nft.loadNFTMarker('../examples/DataNFT/pinball') + print('marker_ids:', marker_ids) + assert marker_ids == [0] + + @pytest.mark.asyncio + async def test_get_nft_data(self): + await self.asyncSetUp() + marker_ids = await self.nft.loadNFTMarker('../examples/DataNFT/pinball') + print('marker_ids:', marker_ids) + if marker_ids: + nftData = self.nft.getNFTData(marker_ids[0]) + print('nftData:', nftData) + assert nftData.width_NFT == 893 + assert nftData.height_NFT == 1117 + assert nftData.dpi_NFT == 120 + else: + pytest.fail("No NFT markers were added.") + + @pytest.mark.asyncio + async def test_detect_nft_marker(self): + await self.asyncSetUp() + await self.nft.loadNFTMarkers(['../examples/DataNFT/pinball']) + self.nft.detectNFTMarker() + info = self.nft.getNFTMarker(0) + print('info:', info) + assert "id" in info + assert info["id"] == 0 + assert info["error"] == -1 + assert info["found"] == 0 + assert info["pose"] == [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] + + @pytest.mark.asyncio + async def test_process_image(self): + await self.asyncSetUp() + await self.nft.loadNFTMarkers(['../examples/DataNFT/pinball']) + + # Load the test image + image_path = './pinball-test.png' + image = Image.open(image_path) + image = image.convert('RGBA') + image_data = np.array(image) + print('image_data:', image_data) + + # Create a mock image object with the data attribute + class MockImage: + def __init__(self, data): + self.data = data + + mock_image = MockImage(image_data) + + # Process the image + self.nft.process(mock_image) + info = None + + # Check the results + # Assuming add_event_listener is a method that takes an event name and a callback function + def on_get_nft_marker(ev): + print("getNFTMarker", ev) + info = ev + print('info:', info) + assert "id" in info + assert info["id"] == 0 + assert info["found"] == 1 + + self.nft.add_event_listener("getNFTMarker", on_get_nft_marker) + + +if __name__ == '__main__': + pytest.main() \ No newline at end of file